@memberjunction/server 2.39.0 → 2.40.0
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/dist/config.d.ts +99 -30
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +29 -13
- package/dist/config.js.map +1 -1
- package/dist/resolvers/AskSkipResolver.d.ts +3 -3
- package/dist/resolvers/AskSkipResolver.d.ts.map +1 -1
- package/dist/resolvers/AskSkipResolver.js +121 -72
- package/dist/resolvers/AskSkipResolver.js.map +1 -1
- package/dist/scheduler/LearningCycleScheduler.d.ts.map +1 -1
- package/dist/scheduler/LearningCycleScheduler.js +4 -1
- package/dist/scheduler/LearningCycleScheduler.js.map +1 -1
- package/package.json +22 -22
- package/src/config.ts +34 -17
- package/src/resolvers/AskSkipResolver.ts +620 -136
- package/src/scheduler/LearningCycleScheduler.ts +7 -2
|
@@ -53,7 +53,7 @@ import {
|
|
|
53
53
|
UserNotificationEntity,
|
|
54
54
|
} from '@memberjunction/core-entities';
|
|
55
55
|
import { DataSource } from 'typeorm';
|
|
56
|
-
import {
|
|
56
|
+
import { apiKey, baseUrl, configInfo, graphqlPort, mj_core_schema } from '../config.js';
|
|
57
57
|
|
|
58
58
|
import { registerEnumType } from 'type-graphql';
|
|
59
59
|
import { MJGlobal, CopyScalarsAndArrays } from '@memberjunction/global';
|
|
@@ -65,9 +65,16 @@ import { deleteAccessToken, GetDataAccessToken, registerAccessToken, tokenExists
|
|
|
65
65
|
import e from 'express';
|
|
66
66
|
import { Skip } from '@graphql-tools/utils';
|
|
67
67
|
|
|
68
|
+
/**
|
|
69
|
+
* Enumeration representing the different phases of a Skip response
|
|
70
|
+
* Corresponds to the lifecycle of a Skip AI interaction
|
|
71
|
+
*/
|
|
68
72
|
enum SkipResponsePhase {
|
|
73
|
+
/** Skip is asking for clarification before proceeding */
|
|
69
74
|
ClarifyingQuestion = 'clarifying_question',
|
|
75
|
+
/** Skip is requesting data from the system to process the request */
|
|
70
76
|
DataRequest = 'data_request',
|
|
77
|
+
/** Skip has completed its analysis and has returned a final response */
|
|
71
78
|
AnalysisComplete = 'analysis_complete',
|
|
72
79
|
}
|
|
73
80
|
|
|
@@ -76,165 +83,267 @@ registerEnumType(SkipResponsePhase, {
|
|
|
76
83
|
description: 'The phase of the respons: clarifying_question, data_request, or analysis_complete',
|
|
77
84
|
});
|
|
78
85
|
|
|
86
|
+
/**
|
|
87
|
+
* Result type for Skip AI interactions
|
|
88
|
+
* Contains the status of the request, the response phase, the result payload,
|
|
89
|
+
* and references to the conversation and message IDs
|
|
90
|
+
*/
|
|
79
91
|
@ObjectType()
|
|
80
92
|
export class AskSkipResultType {
|
|
93
|
+
/** Whether the interaction was successful */
|
|
81
94
|
@Field(() => Boolean)
|
|
82
95
|
Success: boolean;
|
|
83
96
|
|
|
97
|
+
/** Status message of the interaction */
|
|
84
98
|
@Field(() => String)
|
|
85
99
|
Status: string; // required
|
|
86
100
|
|
|
101
|
+
/** The phase of the response from Skip */
|
|
87
102
|
@Field(() => SkipResponsePhase)
|
|
88
103
|
ResponsePhase: SkipResponsePhase;
|
|
89
104
|
|
|
105
|
+
/** The result payload, usually a JSON string of the full response */
|
|
90
106
|
@Field(() => String)
|
|
91
107
|
Result: string;
|
|
92
108
|
|
|
109
|
+
/** The ID of the conversation this interaction belongs to */
|
|
93
110
|
@Field(() => String)
|
|
94
111
|
ConversationId: string;
|
|
95
112
|
|
|
113
|
+
/** The ID of the user message in the conversation */
|
|
96
114
|
@Field(() => String)
|
|
97
115
|
UserMessageConversationDetailId: string;
|
|
98
116
|
|
|
117
|
+
/** The ID of the AI response message in the conversation */
|
|
99
118
|
@Field(() => String)
|
|
100
119
|
AIMessageConversationDetailId: string;
|
|
101
120
|
}
|
|
102
121
|
|
|
122
|
+
/**
|
|
123
|
+
* Result type for manual learning cycle operations
|
|
124
|
+
* Contains success status and a message describing the result
|
|
125
|
+
*/
|
|
103
126
|
@ObjectType()
|
|
104
127
|
export class ManualLearningCycleResultType {
|
|
128
|
+
/** Whether the learning cycle operation was successful */
|
|
105
129
|
@Field(() => Boolean)
|
|
106
130
|
Success: boolean;
|
|
107
131
|
|
|
132
|
+
/** Descriptive message about the learning cycle operation */
|
|
108
133
|
@Field(() => String)
|
|
109
134
|
Message: string;
|
|
110
135
|
}
|
|
111
136
|
|
|
137
|
+
/**
|
|
138
|
+
* Contains details about a specific learning cycle
|
|
139
|
+
* Includes identifier, start time, and duration information
|
|
140
|
+
*/
|
|
112
141
|
@ObjectType()
|
|
113
142
|
export class CycleDetailsType {
|
|
143
|
+
/** Unique identifier for the learning cycle */
|
|
114
144
|
@Field(() => String)
|
|
115
145
|
LearningCycleId: string;
|
|
116
146
|
|
|
147
|
+
/** ISO timestamp when the cycle started */
|
|
117
148
|
@Field(() => String)
|
|
118
149
|
StartTime: string;
|
|
119
150
|
|
|
151
|
+
/** Duration of the cycle in minutes */
|
|
120
152
|
@Field(() => Number)
|
|
121
153
|
RunningForMinutes: number;
|
|
122
154
|
}
|
|
123
155
|
|
|
156
|
+
/**
|
|
157
|
+
* Information about an organization that is currently running a learning cycle
|
|
158
|
+
* Links organization to specific learning cycle and provides timing details
|
|
159
|
+
*/
|
|
124
160
|
@ObjectType()
|
|
125
161
|
export class RunningOrganizationType {
|
|
162
|
+
/** Identifier of the organization running the cycle */
|
|
126
163
|
@Field(() => String)
|
|
127
164
|
OrganizationId: string;
|
|
128
165
|
|
|
166
|
+
/** Unique identifier for the learning cycle */
|
|
129
167
|
@Field(() => String)
|
|
130
168
|
LearningCycleId: string;
|
|
131
169
|
|
|
170
|
+
/** ISO timestamp when the cycle started */
|
|
132
171
|
@Field(() => String)
|
|
133
172
|
StartTime: string;
|
|
134
173
|
|
|
174
|
+
/** Duration the cycle has been running in minutes */
|
|
135
175
|
@Field(() => Number)
|
|
136
176
|
RunningForMinutes: number;
|
|
137
177
|
}
|
|
138
178
|
|
|
179
|
+
/**
|
|
180
|
+
* Status information about the learning cycle scheduler and running cycles
|
|
181
|
+
* Provides overall scheduler status and details about active learning cycles
|
|
182
|
+
*/
|
|
139
183
|
@ObjectType()
|
|
140
184
|
export class LearningCycleStatusType {
|
|
185
|
+
/** Whether the scheduler process is currently active */
|
|
141
186
|
@Field(() => Boolean)
|
|
142
187
|
IsSchedulerRunning: boolean;
|
|
143
188
|
|
|
189
|
+
/** ISO timestamp of the last time the scheduler ran a cycle */
|
|
144
190
|
@Field(() => String, { nullable: true })
|
|
145
191
|
LastRunTime: string;
|
|
146
192
|
|
|
193
|
+
/** List of organizations that are currently running learning cycles */
|
|
147
194
|
@Field(() => [RunningOrganizationType], { nullable: true })
|
|
148
195
|
RunningOrganizations: RunningOrganizationType[];
|
|
149
196
|
}
|
|
150
197
|
|
|
198
|
+
/**
|
|
199
|
+
* Result of an attempt to stop a learning cycle
|
|
200
|
+
* Provides status information about the stop operation
|
|
201
|
+
*/
|
|
151
202
|
@ObjectType()
|
|
152
203
|
export class StopLearningCycleResultType {
|
|
204
|
+
/** Whether the stop operation succeeded */
|
|
153
205
|
@Field(() => Boolean)
|
|
154
206
|
Success: boolean;
|
|
155
207
|
|
|
208
|
+
/** Descriptive message about the result of the stop operation */
|
|
156
209
|
@Field(() => String)
|
|
157
210
|
Message: string;
|
|
158
211
|
|
|
212
|
+
/** Whether the cycle was actually running when the stop was attempted */
|
|
159
213
|
@Field(() => Boolean)
|
|
160
214
|
WasRunning: boolean;
|
|
161
215
|
|
|
216
|
+
/** Details about the cycle that was stopped (if any) */
|
|
162
217
|
@Field(() => CycleDetailsType, { nullable: true })
|
|
163
218
|
CycleDetails: CycleDetailsType;
|
|
164
219
|
}
|
|
165
220
|
|
|
166
221
|
/**
|
|
167
|
-
*
|
|
222
|
+
* This function initializes the Skip learning cycle scheduler. It sets up an event listener for the server's setup complete event and starts the scheduler if learning cycles are enabled and a valid API endpoint is configured.
|
|
223
|
+
*/
|
|
224
|
+
function initializeSkipLearningCycleScheduler() {
|
|
225
|
+
try {
|
|
226
|
+
// Set up event listener for server initialization
|
|
227
|
+
const eventListener = MJGlobal.Instance.GetEventListener(true);
|
|
228
|
+
eventListener.subscribe(event => {
|
|
229
|
+
// Filter for our server's setup complete event
|
|
230
|
+
if (event.eventCode === MJ_SERVER_EVENT_CODE && event.args?.type === 'setupComplete') {
|
|
231
|
+
try {
|
|
232
|
+
const skipConfigInfo = configInfo.askSkip;
|
|
233
|
+
if (!skipConfigInfo) {
|
|
234
|
+
LogStatus('Skip AI Learning Cycle Scheduler not started: Skip configuration not found');
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
if (!skipConfigInfo.learningCycleEnabled) {
|
|
238
|
+
LogStatus('Skip AI Learning Cycles not enabled in configuration');
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Check if we have a valid endpoint when cycles are enabled
|
|
243
|
+
if (!skipConfigInfo.learningCycleURL || skipConfigInfo.learningCycleURL.trim().length === 0) {
|
|
244
|
+
LogError('Skip AI Learning cycle scheduler not started: Learning cycles are enabled but no Learning Cycle API endpoint is configured');
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const dataSources = event.args.dataSources;
|
|
249
|
+
if (dataSources && dataSources.length > 0) {
|
|
250
|
+
// Initialize the scheduler
|
|
251
|
+
const scheduler = LearningCycleScheduler.Instance;
|
|
252
|
+
|
|
253
|
+
// Set the data sources for the scheduler
|
|
254
|
+
scheduler.setDataSources(dataSources);
|
|
255
|
+
|
|
256
|
+
// Default is 60 minutes, if the interval is not set in the config, use 60 minutes
|
|
257
|
+
const interval = skipConfigInfo.learningCycleIntervalInMinutes ?? 60;
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
if (skipConfigInfo.learningCycleRunUponStartup) {
|
|
261
|
+
// If configured to run immediately, run the learning cycle
|
|
262
|
+
LogStatus('Skip API Learning Cycle: Run Upon Startup is enabled, running learning cycle immediately');
|
|
263
|
+
// Start the scheduler
|
|
264
|
+
scheduler.start(interval);
|
|
265
|
+
}
|
|
266
|
+
else {
|
|
267
|
+
// not asked to start right away, just start the scheduler after the interval
|
|
268
|
+
LogStatus(`Skip API Learning Cycle: Scheduler first run will start after interval of ${interval} minutes. If you want a learing cycle to run immediately, set the learningCycleRunUponStartup property in the config file to true.`);
|
|
269
|
+
|
|
270
|
+
// create a one time timer to start the scheduler
|
|
271
|
+
setTimeout(() => {
|
|
272
|
+
LogStatus(`Skip API Learning Cycle: Starting scheduler after ${interval} minutes. If you want a learing cycle to run immediately, set the learningCycleRunUponStartup property in the config file to true.`);
|
|
273
|
+
scheduler.start(interval);
|
|
274
|
+
}, interval * 60 * 1000); // convert minutes to milliseconds
|
|
275
|
+
}
|
|
276
|
+
} else {
|
|
277
|
+
LogError('Cannot initialize Skip learning cycle scheduler: No data sources available');
|
|
278
|
+
}
|
|
279
|
+
} catch (error) {
|
|
280
|
+
LogError(`Error initializing Skip learning cycle scheduler: ${error}`);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
} catch (error) {
|
|
285
|
+
// Handle any errors from the static initializer
|
|
286
|
+
LogError(`Failed to initialize Skip learning cycle scheduler: ${error}`);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
// now call the function to initialize the scheduler
|
|
290
|
+
initializeSkipLearningCycleScheduler();
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Base type for Skip API requests containing common fields
|
|
294
|
+
* Used as the foundation for both chat and learning cycle requests
|
|
168
295
|
*/
|
|
169
296
|
type BaseSkipRequest = {
|
|
297
|
+
/** Entity metadata to send to Skip */
|
|
170
298
|
entities: SkipEntityInfo[],
|
|
299
|
+
/** Query metadata to send to Skip */
|
|
171
300
|
queries: SkipQueryInfo[],
|
|
301
|
+
/** Agent notes to send to Skip */
|
|
172
302
|
notes: SkipAPIAgentNote[],
|
|
303
|
+
/** Note type definitions to send to Skip */
|
|
173
304
|
noteTypes: SkipAPIAgentNoteType[],
|
|
305
|
+
/** Agent requests to send to Skip */
|
|
174
306
|
requests: SkipAPIAgentRequest[],
|
|
307
|
+
/** Access token for authorizing Skip to call back to MemberJunction */
|
|
175
308
|
accessToken: GetDataAccessToken,
|
|
309
|
+
/** Organization identifier */
|
|
176
310
|
organizationID: string,
|
|
311
|
+
/** Additional organization-specific information */
|
|
177
312
|
organizationInfo: any,
|
|
313
|
+
/** API keys for various AI services to be used by Skip */
|
|
178
314
|
apiKeys: SkipAPIRequestAPIKey[],
|
|
315
|
+
/** URL of the calling server for callback purposes */
|
|
179
316
|
callingServerURL: string,
|
|
317
|
+
/** API key for the calling server */
|
|
180
318
|
callingServerAPIKey: string,
|
|
319
|
+
/** Access token for the calling server */
|
|
181
320
|
callingServerAccessToken: string
|
|
182
321
|
}
|
|
322
|
+
/**
|
|
323
|
+
* Resolver for Skip AI interactions
|
|
324
|
+
* Handles conversations with Skip, learning cycles, and related operations.
|
|
325
|
+
* Skip is an AI agent that can analyze data, answer questions, and learn from interactions.
|
|
326
|
+
*/
|
|
183
327
|
@Resolver(AskSkipResultType)
|
|
184
328
|
export class AskSkipResolver {
|
|
329
|
+
/** Default name for new conversations */
|
|
185
330
|
private static _defaultNewChatName = 'New Chat';
|
|
186
331
|
|
|
187
|
-
|
|
188
|
-
static {
|
|
189
|
-
try {
|
|
190
|
-
// Set up event listener for server initialization
|
|
191
|
-
const eventListener = MJGlobal.Instance.GetEventListener(true);
|
|
192
|
-
eventListener.subscribe(event => {
|
|
193
|
-
// Filter for our server's setup complete event
|
|
194
|
-
if (event.eventCode === MJ_SERVER_EVENT_CODE && event.args?.type === 'setupComplete') {
|
|
195
|
-
try {
|
|
196
|
-
if (___skipRunLearningCycles !== 'Y') {
|
|
197
|
-
LogStatus('Skip AI Learning cycle scheduler not started: Disabled in configuration');
|
|
198
|
-
return;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
// Check if we have a valid endpoint when cycles are enabled
|
|
202
|
-
if (!___skipLearningAPIurl || ___skipLearningAPIurl.trim() === '') {
|
|
203
|
-
LogError('Skip AI Learning cycle scheduler not started: Learning cycles are enabled but no API endpoint is configured');
|
|
204
|
-
return;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
const dataSources = event.args.dataSources;
|
|
208
|
-
if (dataSources && dataSources.length > 0) {
|
|
209
|
-
// Initialize the scheduler
|
|
210
|
-
const scheduler = LearningCycleScheduler.Instance;
|
|
211
|
-
|
|
212
|
-
// Set the data sources for the scheduler
|
|
213
|
-
scheduler.setDataSources(dataSources);
|
|
214
|
-
|
|
215
|
-
// Default is 60 minutes, if the interval is not set in the config, use 60 minutes
|
|
216
|
-
const interval = ___skipLearningCycleIntervalInMinutes ?? 60;
|
|
217
|
-
scheduler.start(interval);
|
|
218
|
-
} else {
|
|
219
|
-
LogError('Cannot initialize Skip learning cycle scheduler: No data sources available');
|
|
220
|
-
}
|
|
221
|
-
} catch (error) {
|
|
222
|
-
LogError(`Error initializing Skip learning cycle scheduler: ${error}`);
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
});
|
|
226
|
-
} catch (error) {
|
|
227
|
-
// Handle any errors from the static initializer
|
|
228
|
-
LogError(`Failed to initialize Skip learning cycle scheduler: ${error}`);
|
|
229
|
-
}
|
|
230
|
-
}
|
|
332
|
+
/** Maximum number of historical messages to include in a conversation context */
|
|
231
333
|
private static _maxHistoricalMessages = 30;
|
|
232
334
|
|
|
233
335
|
/**
|
|
234
|
-
* Handles a
|
|
235
|
-
*
|
|
236
|
-
*
|
|
237
|
-
* @param
|
|
336
|
+
* Handles a chat interaction with Skip about a specific data record
|
|
337
|
+
* Allows users to ask questions about a particular entity record
|
|
338
|
+
*
|
|
339
|
+
* @param UserQuestion The question or message from the user
|
|
340
|
+
* @param ConversationId ID of an existing conversation, or empty for a new conversation
|
|
341
|
+
* @param EntityName The name of the entity the record belongs to
|
|
342
|
+
* @param compositeKey The primary key values that identify the specific record
|
|
343
|
+
* @param dataSource Database connection
|
|
344
|
+
* @param userPayload Information about the authenticated user
|
|
345
|
+
* @param pubSub Publisher/subscriber for events
|
|
346
|
+
* @returns Result of the Skip interaction
|
|
238
347
|
*/
|
|
239
348
|
@Query(() => AskSkipResultType)
|
|
240
349
|
async ExecuteAskSkipRecordChat(
|
|
@@ -309,16 +418,26 @@ export class AskSkipResolver {
|
|
|
309
418
|
return this.handleSimpleSkipChatPostRequest(input, convoEntity.ID, convoDetailEntity.ID, true, user);
|
|
310
419
|
}
|
|
311
420
|
|
|
421
|
+
/**
|
|
422
|
+
* Executes a Skip learning cycle
|
|
423
|
+
* Learning cycles allow Skip to analyze conversations and improve its knowledge and capabilities
|
|
424
|
+
*
|
|
425
|
+
* @param dataSource Database connection
|
|
426
|
+
* @param userPayload Information about the authenticated user
|
|
427
|
+
* @param ForceEntityRefresh Whether to force a refresh of entity metadata
|
|
428
|
+
* @returns Result of the learning cycle execution
|
|
429
|
+
*/
|
|
312
430
|
@Mutation(() => AskSkipResultType)
|
|
313
431
|
async ExecuteAskSkipLearningCycle(
|
|
314
432
|
@Ctx() { dataSource, userPayload }: AppContext,
|
|
315
433
|
@Arg('ForceEntityRefresh', () => Boolean, { nullable: true }) ForceEntityRefresh?: boolean
|
|
316
434
|
) {
|
|
435
|
+
const skipConfigInfo = configInfo.askSkip;
|
|
317
436
|
// First check if learning cycles are enabled in configuration
|
|
318
|
-
if (
|
|
437
|
+
if (!skipConfigInfo.learningCycleEnabled) {
|
|
319
438
|
return {
|
|
320
439
|
success: false,
|
|
321
|
-
error: 'Learning cycles are
|
|
440
|
+
error: 'Learning cycles are not enabled in configuration',
|
|
322
441
|
elapsedTime: 0,
|
|
323
442
|
noteChanges: [],
|
|
324
443
|
queryChanges: [],
|
|
@@ -327,7 +446,7 @@ export class AskSkipResolver {
|
|
|
327
446
|
}
|
|
328
447
|
|
|
329
448
|
// Check if we have a valid endpoint when cycles are enabled
|
|
330
|
-
if (!
|
|
449
|
+
if (!skipConfigInfo.learningCycleURL || skipConfigInfo.learningCycleURL.trim().length === 0) {
|
|
331
450
|
return {
|
|
332
451
|
success: false,
|
|
333
452
|
error: 'Learning cycle API endpoint is not configured',
|
|
@@ -347,7 +466,7 @@ export class AskSkipResolver {
|
|
|
347
466
|
await AIEngine.Instance.Config(false, user);
|
|
348
467
|
|
|
349
468
|
// Check if this organization is already running a learning cycle using their organization ID
|
|
350
|
-
const organizationId =
|
|
469
|
+
const organizationId = skipConfigInfo.orgID;
|
|
351
470
|
const scheduler = LearningCycleScheduler.Instance;
|
|
352
471
|
const runningStatus = scheduler.isOrganizationRunningCycle(organizationId);
|
|
353
472
|
|
|
@@ -396,55 +515,91 @@ export class AskSkipResolver {
|
|
|
396
515
|
// Build the request to Skip learning API
|
|
397
516
|
LogStatus(`Building Skip Learning API request`);
|
|
398
517
|
const input = await this.buildSkipLearningAPIRequest(learningCycleId, lastCompleteLearningCycleDate, true, true, true, false, dataSource, user, ForceEntityRefresh || false);
|
|
518
|
+
if (input.newConversations.length === 0) {
|
|
519
|
+
// no new conversations to process
|
|
520
|
+
LogStatus(` Skip Learning Cycles: No new conversations to process for learning cycle`);
|
|
521
|
+
learningCycleEntity.Status = 'Complete';
|
|
522
|
+
learningCycleEntity.AgentSummary = 'No new conversations to process, learning cycle skipped, but recorded for audit purposes.';
|
|
523
|
+
learningCycleEntity.EndedAt = new Date();
|
|
524
|
+
if (!(await learningCycleEntity.Save())) {
|
|
525
|
+
LogError(`Failed to update learning cycle record: ${learningCycleEntity.LatestResult.Error}`);
|
|
526
|
+
}
|
|
527
|
+
const result: SkipAPILearningCycleResponse = {
|
|
528
|
+
success: true,
|
|
529
|
+
learningCycleSkipped: true,
|
|
530
|
+
elapsedTime: 0,
|
|
531
|
+
noteChanges: [],
|
|
532
|
+
queryChanges: [],
|
|
533
|
+
requestChanges: [],
|
|
534
|
+
}
|
|
535
|
+
return result;
|
|
536
|
+
}
|
|
537
|
+
else {
|
|
538
|
+
// Make the API request
|
|
539
|
+
const response = await this.handleSimpleSkipLearningPostRequest(input, user, learningCycleId, agentID);
|
|
399
540
|
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
// Update learning cycle to completed
|
|
404
|
-
const endTime = new Date();
|
|
405
|
-
const elapsedTimeMs = endTime.getTime() - startTime.getTime();
|
|
541
|
+
// Update learning cycle to completed
|
|
542
|
+
const endTime = new Date();
|
|
543
|
+
const elapsedTimeMs = endTime.getTime() - startTime.getTime();
|
|
406
544
|
|
|
407
|
-
|
|
545
|
+
LogStatus(`Learning cycle finished with status: ${response.success ? 'Success' : 'Failed'} in ${elapsedTimeMs / 1000} seconds`);
|
|
408
546
|
|
|
409
|
-
|
|
410
|
-
|
|
547
|
+
learningCycleEntity.Status = response.success ? 'Complete' : 'Failed';
|
|
548
|
+
learningCycleEntity.EndedAt = endTime;
|
|
411
549
|
|
|
412
|
-
|
|
413
|
-
|
|
550
|
+
if (!(await learningCycleEntity.Save())) {
|
|
551
|
+
LogError(`Failed to update learning cycle record: ${learningCycleEntity.LatestResult.Error}`);
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
return response;
|
|
414
555
|
}
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
scheduler.unregisterRunningCycle(organizationId);
|
|
418
|
-
|
|
419
|
-
return response;
|
|
420
|
-
} catch (error) {
|
|
556
|
+
}
|
|
557
|
+
catch (error) {
|
|
421
558
|
// Make sure to update the learning cycle record as failed
|
|
422
559
|
learningCycleEntity.Status = 'Failed';
|
|
423
560
|
learningCycleEntity.EndedAt = new Date();
|
|
424
561
|
|
|
425
562
|
try {
|
|
426
563
|
await learningCycleEntity.Save();
|
|
427
|
-
}
|
|
564
|
+
}
|
|
565
|
+
catch (saveError) {
|
|
428
566
|
LogError(`Failed to update learning cycle record after error: ${saveError}`);
|
|
429
567
|
}
|
|
430
568
|
|
|
431
|
-
// Unregister the organization on error
|
|
432
|
-
scheduler.unregisterRunningCycle(organizationId);
|
|
433
|
-
|
|
434
569
|
// Re-throw the original error
|
|
435
570
|
throw error;
|
|
436
571
|
}
|
|
572
|
+
finally {
|
|
573
|
+
// Unregister the cycle/organizationId safely
|
|
574
|
+
try {
|
|
575
|
+
scheduler.unregisterRunningCycle(organizationId);
|
|
576
|
+
}
|
|
577
|
+
catch (error) {
|
|
578
|
+
LogError(`Failed to unregister organization ${organizationId} from running cycles: ${error}`);
|
|
579
|
+
}
|
|
580
|
+
}
|
|
437
581
|
}
|
|
438
582
|
|
|
583
|
+
/**
|
|
584
|
+
* Handles the HTTP POST request to the Skip learning cycle API
|
|
585
|
+
* Sends the learning cycle request and processes the response
|
|
586
|
+
*
|
|
587
|
+
* @param input The learning cycle request payload
|
|
588
|
+
* @param user User context for the request
|
|
589
|
+
* @param learningCycleId ID of the current learning cycle
|
|
590
|
+
* @param agentID ID of the Skip agent
|
|
591
|
+
* @returns Response from the Skip learning cycle API
|
|
592
|
+
*/
|
|
439
593
|
protected async handleSimpleSkipLearningPostRequest(
|
|
440
594
|
input: SkipAPILearningCycleRequest,
|
|
441
595
|
user: UserInfo,
|
|
442
596
|
learningCycleId: string,
|
|
443
597
|
agentID: string
|
|
444
598
|
): Promise<SkipAPILearningCycleResponse> {
|
|
445
|
-
|
|
599
|
+
const skipConfigInfo = configInfo.askSkip;
|
|
600
|
+
LogStatus(` >>> HandleSimpleSkipLearningPostRequest Sending request to Skip API: ${skipConfigInfo.learningCycleURL}`);
|
|
446
601
|
|
|
447
|
-
const response = await sendPostRequest(
|
|
602
|
+
const response = await sendPostRequest(skipConfigInfo.learningCycleURL, input, true, null);
|
|
448
603
|
|
|
449
604
|
if (response && response.length > 0) {
|
|
450
605
|
// the last object in the response array is the final response from the Skip API
|
|
@@ -482,6 +637,17 @@ export class AskSkipResolver {
|
|
|
482
637
|
}
|
|
483
638
|
}
|
|
484
639
|
|
|
640
|
+
/**
|
|
641
|
+
* Handles the HTTP POST request to the Skip chat API
|
|
642
|
+
* Sends the chat request and processes the response
|
|
643
|
+
*
|
|
644
|
+
* @param input The chat request payload
|
|
645
|
+
* @param conversationID ID of the conversation, or empty for a new conversation
|
|
646
|
+
* @param UserMessageConversationDetailId ID of the user's message in the conversation
|
|
647
|
+
* @param createAIMessageConversationDetail Whether to create a conversation detail for the AI response
|
|
648
|
+
* @param user User context for the request
|
|
649
|
+
* @returns Result of the Skip interaction
|
|
650
|
+
*/
|
|
485
651
|
protected async handleSimpleSkipChatPostRequest(
|
|
486
652
|
input: SkipAPIRequest,
|
|
487
653
|
conversationID: string = '',
|
|
@@ -489,9 +655,10 @@ export class AskSkipResolver {
|
|
|
489
655
|
createAIMessageConversationDetail: boolean = false,
|
|
490
656
|
user: UserInfo = null
|
|
491
657
|
): Promise<AskSkipResultType> {
|
|
492
|
-
|
|
658
|
+
const skipConfigInfo = configInfo.askSkip;
|
|
659
|
+
LogStatus(` >>> HandleSimpleSkipChatPostRequest Sending request to Skip API: ${skipConfigInfo.chatURL}`);
|
|
493
660
|
|
|
494
|
-
const response = await sendPostRequest(
|
|
661
|
+
const response = await sendPostRequest(skipConfigInfo.chatURL, input, true, null);
|
|
495
662
|
|
|
496
663
|
if (response && response.length > 0) {
|
|
497
664
|
// the last object in the response array is the final response from the Skip API
|
|
@@ -524,9 +691,13 @@ export class AskSkipResolver {
|
|
|
524
691
|
}
|
|
525
692
|
|
|
526
693
|
/**
|
|
527
|
-
* Processes note changes received from the Skip API learning cycle
|
|
694
|
+
* Processes note changes received from the Skip API learning cycle
|
|
695
|
+
* Applies changes to agent notes based on the learning cycle response
|
|
696
|
+
*
|
|
528
697
|
* @param noteChanges Changes to agent notes
|
|
529
|
-
* @param
|
|
698
|
+
* @param agentID ID of the Skip agent
|
|
699
|
+
* @param user User context for the request
|
|
700
|
+
* @returns Promise that resolves when processing is complete
|
|
530
701
|
*/
|
|
531
702
|
protected async processLearningCycleNoteChanges(
|
|
532
703
|
noteChanges: SkipLearningCycleNoteChange[],
|
|
@@ -560,6 +731,15 @@ export class AskSkipResolver {
|
|
|
560
731
|
}));
|
|
561
732
|
}
|
|
562
733
|
|
|
734
|
+
/**
|
|
735
|
+
* Processes an add or update operation for a Skip agent note
|
|
736
|
+
* Creates a new note or updates an existing one based on the change type
|
|
737
|
+
*
|
|
738
|
+
* @param change The note change information
|
|
739
|
+
* @param agentID ID of the Skip agent
|
|
740
|
+
* @param user User context for the operation
|
|
741
|
+
* @returns Whether the operation was successful
|
|
742
|
+
*/
|
|
563
743
|
protected async processAddOrUpdateSkipNote(change: SkipLearningCycleNoteChange, agentID: string, user: UserInfo): Promise<boolean> {
|
|
564
744
|
try {
|
|
565
745
|
// Get the note entity object
|
|
@@ -574,17 +754,11 @@ export class AskSkipResolver {
|
|
|
574
754
|
return false;
|
|
575
755
|
}
|
|
576
756
|
} else {
|
|
577
|
-
// For new notes, ensure the note type is not "Human"
|
|
578
|
-
if (change.note.agentNoteType === "Human") {
|
|
579
|
-
LogStatus(`WARNING: Cannot create a new Human note with the learning cycle. Operation ignored.`);
|
|
580
|
-
return false;
|
|
581
|
-
}
|
|
582
|
-
|
|
583
757
|
// Create a new note
|
|
584
758
|
noteEntity.NewRecord();
|
|
585
759
|
noteEntity.AgentID = agentID;
|
|
586
760
|
}
|
|
587
|
-
noteEntity.AgentNoteTypeID = this.getAgentNoteTypeIDByName('AI');
|
|
761
|
+
noteEntity.AgentNoteTypeID = this.getAgentNoteTypeIDByName('AI'); // always set to AI
|
|
588
762
|
noteEntity.Note = change.note.note;
|
|
589
763
|
noteEntity.Type = change.note.type;
|
|
590
764
|
|
|
@@ -605,6 +779,14 @@ export class AskSkipResolver {
|
|
|
605
779
|
}
|
|
606
780
|
}
|
|
607
781
|
|
|
782
|
+
/**
|
|
783
|
+
* Processes a delete operation for a Skip agent note
|
|
784
|
+
* Removes the specified note from the database
|
|
785
|
+
*
|
|
786
|
+
* @param change The note change information
|
|
787
|
+
* @param user User context for the operation
|
|
788
|
+
* @returns Whether the deletion was successful
|
|
789
|
+
*/
|
|
608
790
|
protected async processDeleteSkipNote(change: SkipLearningCycleNoteChange, user: UserInfo): Promise<boolean> {
|
|
609
791
|
// Get the note entity object
|
|
610
792
|
const md = new Metadata();
|
|
@@ -634,6 +816,15 @@ cycle.`);
|
|
|
634
816
|
return true;
|
|
635
817
|
}
|
|
636
818
|
|
|
819
|
+
/**
|
|
820
|
+
* Creates a conversation detail entry for an AI message
|
|
821
|
+
* Stores the AI response in the conversation history
|
|
822
|
+
*
|
|
823
|
+
* @param apiResponse The response from the Skip API
|
|
824
|
+
* @param conversationID ID of the conversation
|
|
825
|
+
* @param user User context for the operation
|
|
826
|
+
* @returns ID of the created conversation detail, or empty string if creation failed
|
|
827
|
+
*/
|
|
637
828
|
protected async CreateAIMessageConversationDetail(apiResponse: SkipAPIResponse, conversationID: string, user: UserInfo): Promise<string> {
|
|
638
829
|
const md = new Metadata();
|
|
639
830
|
const convoDetailEntityAI = <ConversationDetailEntity>await md.GetEntityObject('Conversation Details', user);
|
|
@@ -658,11 +849,14 @@ cycle.`);
|
|
|
658
849
|
|
|
659
850
|
/**
|
|
660
851
|
* Builds the base Skip API request with common fields and data
|
|
852
|
+
* Creates the foundation for both chat and learning cycle requests
|
|
853
|
+
*
|
|
661
854
|
* @param contextUser The user making the request
|
|
662
855
|
* @param dataSource The data source to use
|
|
663
856
|
* @param includeEntities Whether to include entities in the request
|
|
664
857
|
* @param includeQueries Whether to include queries in the request
|
|
665
858
|
* @param includeNotes Whether to include agent notes in the request
|
|
859
|
+
* @param includeRequests Whether to include agent requests in the request
|
|
666
860
|
* @param forceEntitiesRefresh Whether to force refresh of entities
|
|
667
861
|
* @param includeCallBackKeyAndAccessToken Whether to include a callback key and access token
|
|
668
862
|
* @param additionalTokenInfo Additional info to include in the access token
|
|
@@ -674,15 +868,16 @@ cycle.`);
|
|
|
674
868
|
includeEntities: boolean,
|
|
675
869
|
includeQueries: boolean,
|
|
676
870
|
includeNotes: boolean,
|
|
871
|
+
filterUserNotesToContextUser: boolean,
|
|
677
872
|
includeRequests: boolean,
|
|
678
873
|
forceEntitiesRefresh: boolean = false,
|
|
679
874
|
includeCallBackKeyAndAccessToken: boolean = false,
|
|
680
875
|
additionalTokenInfo: any = {}
|
|
681
876
|
): Promise<BaseSkipRequest> {
|
|
682
|
-
|
|
877
|
+
const skipConfigInfo = configInfo.askSkip;
|
|
683
878
|
const entities = includeEntities ? await this.BuildSkipEntities(dataSource, forceEntitiesRefresh) : [];
|
|
684
879
|
const queries = includeQueries ? this.BuildSkipQueries() : [];
|
|
685
|
-
const {notes, noteTypes} = includeNotes ? await this.BuildSkipAgentNotes(contextUser) : {notes: [], noteTypes: []};
|
|
880
|
+
const {notes, noteTypes} = includeNotes ? await this.BuildSkipAgentNotes(contextUser, filterUserNotesToContextUser) : {notes: [], noteTypes: []};
|
|
686
881
|
const requests = includeRequests ? await this.BuildSkipRequests(contextUser) : [];
|
|
687
882
|
|
|
688
883
|
// Setup access token if needed
|
|
@@ -710,7 +905,7 @@ cycle.`);
|
|
|
710
905
|
noteTypes,
|
|
711
906
|
requests,
|
|
712
907
|
accessToken,
|
|
713
|
-
organizationID:
|
|
908
|
+
organizationID: skipConfigInfo.orgID,
|
|
714
909
|
organizationInfo: configInfo?.askSkip?.organizationInfo,
|
|
715
910
|
apiKeys: this.buildSkipAPIKeys(),
|
|
716
911
|
callingServerURL: accessToken ? `${baseUrl}:${graphqlPort}` : undefined,
|
|
@@ -721,6 +916,19 @@ cycle.`);
|
|
|
721
916
|
|
|
722
917
|
/**
|
|
723
918
|
* Builds the learning API request for Skip
|
|
919
|
+
* Creates a request specific to the learning cycle operation
|
|
920
|
+
*
|
|
921
|
+
* @param learningCycleId ID of the current learning cycle
|
|
922
|
+
* @param lastLearningCycleDate Date of the last completed learning cycle
|
|
923
|
+
* @param includeEntities Whether to include entities in the request
|
|
924
|
+
* @param includeQueries Whether to include queries in the request
|
|
925
|
+
* @param includeNotes Whether to include agent notes in the request
|
|
926
|
+
* @param includeRequests Whether to include agent requests in the request
|
|
927
|
+
* @param dataSource Database connection
|
|
928
|
+
* @param contextUser User context for the request
|
|
929
|
+
* @param forceEntitiesRefresh Whether to force refresh of entities
|
|
930
|
+
* @param includeCallBackKeyAndAccessToken Whether to include a callback key and access token
|
|
931
|
+
* @returns Complete learning cycle request object
|
|
724
932
|
*/
|
|
725
933
|
protected async buildSkipLearningAPIRequest(
|
|
726
934
|
learningCycleId: string,
|
|
@@ -741,6 +949,7 @@ cycle.`);
|
|
|
741
949
|
includeEntities,
|
|
742
950
|
includeQueries,
|
|
743
951
|
includeNotes,
|
|
952
|
+
false,
|
|
744
953
|
includeRequests,
|
|
745
954
|
forceEntitiesRefresh,
|
|
746
955
|
includeCallBackKeyAndAccessToken
|
|
@@ -768,11 +977,14 @@ cycle.`);
|
|
|
768
977
|
}
|
|
769
978
|
|
|
770
979
|
/**
|
|
771
|
-
* Loads the conversations that have
|
|
772
|
-
*
|
|
773
|
-
*
|
|
774
|
-
* @param
|
|
775
|
-
|
|
980
|
+
* Loads the conversations that have been updated or added since the last learning cycle
|
|
981
|
+
* These are used to train Skip and improve its understanding
|
|
982
|
+
*
|
|
983
|
+
* @param lastLearningCycleDate The date of the last learning cycle
|
|
984
|
+
* @param dataSource Database connection
|
|
985
|
+
* @param contextUser User context for the request
|
|
986
|
+
* @returns Array of conversations that are new or have been updated since the last cycle
|
|
987
|
+
*/
|
|
776
988
|
protected async BuildSkipLearningCycleNewConversations(
|
|
777
989
|
lastLearningCycleDate: Date,
|
|
778
990
|
dataSource: DataSource,
|
|
@@ -813,9 +1025,11 @@ cycle.`);
|
|
|
813
1025
|
}
|
|
814
1026
|
|
|
815
1027
|
/**
|
|
816
|
-
* Builds an array of agent requests
|
|
817
|
-
*
|
|
818
|
-
*
|
|
1028
|
+
* Builds an array of agent requests
|
|
1029
|
+
* These are requests that have been made to the AI agent
|
|
1030
|
+
*
|
|
1031
|
+
* @param contextUser User context for loading the requests
|
|
1032
|
+
* @returns Array of agent request objects
|
|
819
1033
|
*/
|
|
820
1034
|
protected async BuildSkipRequests(
|
|
821
1035
|
contextUser: UserInfo
|
|
@@ -852,6 +1066,14 @@ cycle.`);
|
|
|
852
1066
|
}
|
|
853
1067
|
}
|
|
854
1068
|
|
|
1069
|
+
/**
|
|
1070
|
+
* Gets the date of the last complete learning cycle for the Skip agent
|
|
1071
|
+
* Used to determine which data to include in the next learning cycle
|
|
1072
|
+
*
|
|
1073
|
+
* @param agentID ID of the Skip agent
|
|
1074
|
+
* @param user User context for the query
|
|
1075
|
+
* @returns Date of the last complete learning cycle, or epoch if none exists
|
|
1076
|
+
*/
|
|
855
1077
|
protected async GetLastCompleteLearningCycleDate(agentID: string, user: UserInfo): Promise<Date> {
|
|
856
1078
|
const md = new Metadata();
|
|
857
1079
|
const rv = new RunView();
|
|
@@ -877,6 +1099,21 @@ cycle.`);
|
|
|
877
1099
|
|
|
878
1100
|
/**
|
|
879
1101
|
* Builds the chat API request for Skip
|
|
1102
|
+
* Creates a request specific to a chat interaction
|
|
1103
|
+
*
|
|
1104
|
+
* @param messages Array of messages in the conversation
|
|
1105
|
+
* @param conversationId ID of the conversation
|
|
1106
|
+
* @param dataContext Data context associated with the conversation
|
|
1107
|
+
* @param requestPhase The phase of the request (initial, clarifying, etc.)
|
|
1108
|
+
* @param includeEntities Whether to include entities in the request
|
|
1109
|
+
* @param includeQueries Whether to include queries in the request
|
|
1110
|
+
* @param includeNotes Whether to include agent notes in the request
|
|
1111
|
+
* @param includeRequests Whether to include agent requests in the request
|
|
1112
|
+
* @param contextUser User context for the request
|
|
1113
|
+
* @param dataSource Database connection
|
|
1114
|
+
* @param forceEntitiesRefresh Whether to force refresh of entities
|
|
1115
|
+
* @param includeCallBackKeyAndAccessToken Whether to include a callback key and access token
|
|
1116
|
+
* @returns Complete chat request object
|
|
880
1117
|
*/
|
|
881
1118
|
protected async buildSkipChatAPIRequest(
|
|
882
1119
|
messages: SkipMessage[],
|
|
@@ -905,6 +1142,7 @@ cycle.`);
|
|
|
905
1142
|
includeEntities,
|
|
906
1143
|
includeQueries,
|
|
907
1144
|
includeNotes,
|
|
1145
|
+
true,
|
|
908
1146
|
includeRequests,
|
|
909
1147
|
forceEntitiesRefresh,
|
|
910
1148
|
includeCallBackKeyAndAccessToken,
|
|
@@ -927,12 +1165,13 @@ cycle.`);
|
|
|
927
1165
|
}
|
|
928
1166
|
|
|
929
1167
|
/**
|
|
930
|
-
* Builds up an array of
|
|
931
|
-
*
|
|
932
|
-
*
|
|
933
|
-
* @param
|
|
934
|
-
* @param
|
|
935
|
-
* @
|
|
1168
|
+
* Builds up an array of artifacts associated with a conversation
|
|
1169
|
+
* Artifacts are content or documents generated during conversations
|
|
1170
|
+
*
|
|
1171
|
+
* @param contextUser User context for the query
|
|
1172
|
+
* @param dataSource Database connection
|
|
1173
|
+
* @param conversationId ID of the conversation
|
|
1174
|
+
* @returns Array of artifacts associated with the conversation
|
|
936
1175
|
*/
|
|
937
1176
|
protected async buildSkipAPIArtifacts(contextUser: UserInfo, dataSource: DataSource, conversationId: string): Promise<SkipAPIArtifact[]> {
|
|
938
1177
|
const md = new Metadata();
|
|
@@ -1007,10 +1246,15 @@ cycle.`);
|
|
|
1007
1246
|
|
|
1008
1247
|
|
|
1009
1248
|
/**
|
|
1010
|
-
* Executes a script in the context of a data context
|
|
1011
|
-
*
|
|
1012
|
-
*
|
|
1013
|
-
* @param
|
|
1249
|
+
* Executes a script in the context of a data context
|
|
1250
|
+
* Allows running code against data context objects
|
|
1251
|
+
*
|
|
1252
|
+
* @param dataSource Database connection
|
|
1253
|
+
* @param userPayload Information about the authenticated user
|
|
1254
|
+
* @param pubSub Publisher/subscriber for events
|
|
1255
|
+
* @param DataContextId ID of the data context to run the script against
|
|
1256
|
+
* @param ScriptText The script to execute
|
|
1257
|
+
* @returns Result of the script execution
|
|
1014
1258
|
*/
|
|
1015
1259
|
@Query(() => AskSkipResultType)
|
|
1016
1260
|
async ExecuteAskSkipRunScript(
|
|
@@ -1028,6 +1272,12 @@ cycle.`);
|
|
|
1028
1272
|
return this.handleSimpleSkipChatPostRequest(input);
|
|
1029
1273
|
}
|
|
1030
1274
|
|
|
1275
|
+
/**
|
|
1276
|
+
* Builds the array of API keys for various AI services
|
|
1277
|
+
* These are used by Skip to call external AI services
|
|
1278
|
+
*
|
|
1279
|
+
* @returns Array of API keys for different vendor services
|
|
1280
|
+
*/
|
|
1031
1281
|
protected buildSkipAPIKeys(): SkipAPIRequestAPIKey[] {
|
|
1032
1282
|
return [
|
|
1033
1283
|
{
|
|
@@ -1053,6 +1303,19 @@ cycle.`);
|
|
|
1053
1303
|
];
|
|
1054
1304
|
}
|
|
1055
1305
|
|
|
1306
|
+
/**
|
|
1307
|
+
* Executes an analysis query with Skip
|
|
1308
|
+
* This is the primary entry point for general Skip conversations
|
|
1309
|
+
*
|
|
1310
|
+
* @param UserQuestion The question or message from the user
|
|
1311
|
+
* @param ConversationId ID of an existing conversation, or empty for a new conversation
|
|
1312
|
+
* @param dataSource Database connection
|
|
1313
|
+
* @param userPayload Information about the authenticated user
|
|
1314
|
+
* @param pubSub Publisher/subscriber for events
|
|
1315
|
+
* @param DataContextId Optional ID of a data context to use
|
|
1316
|
+
* @param ForceEntityRefresh Whether to force a refresh of entity metadata
|
|
1317
|
+
* @returns Result of the Skip interaction
|
|
1318
|
+
*/
|
|
1056
1319
|
@Query(() => AskSkipResultType)
|
|
1057
1320
|
async ExecuteAskSkipAnalysisQuery(
|
|
1058
1321
|
@Arg('UserQuestion', () => String) UserQuestion: string,
|
|
@@ -1104,8 +1367,11 @@ cycle.`);
|
|
|
1104
1367
|
}
|
|
1105
1368
|
|
|
1106
1369
|
/**
|
|
1107
|
-
* Packages up
|
|
1108
|
-
*
|
|
1370
|
+
* Packages up queries from the metadata based on their status
|
|
1371
|
+
* Used to provide Skip with information about available queries
|
|
1372
|
+
*
|
|
1373
|
+
* @param status The status of queries to include
|
|
1374
|
+
* @returns Array of query information objects
|
|
1109
1375
|
*/
|
|
1110
1376
|
protected BuildSkipQueries(status: "Pending" | "In-Review" | "Approved" | "Rejected" | "Obsolete" = 'Approved'): SkipQueryInfo[] {
|
|
1111
1377
|
const md = new Metadata();
|
|
@@ -1149,9 +1415,13 @@ cycle.`);
|
|
|
1149
1415
|
}
|
|
1150
1416
|
|
|
1151
1417
|
/**
|
|
1152
|
-
* Builds up the array of notes
|
|
1418
|
+
* Builds up the array of notes and note types for Skip
|
|
1419
|
+
* These notes are used to provide Skip with domain knowledge and context
|
|
1420
|
+
*
|
|
1421
|
+
* @param contextUser User context for the request
|
|
1422
|
+
* @returns Object containing arrays of notes and note types
|
|
1153
1423
|
*/
|
|
1154
|
-
protected async BuildSkipAgentNotes(contextUser: UserInfo): Promise<{notes: SkipAPIAgentNote[], noteTypes: SkipAPIAgentNoteType[]}> {
|
|
1424
|
+
protected async BuildSkipAgentNotes(contextUser: UserInfo, filterUserNotesToContextUser: boolean): Promise<{notes: SkipAPIAgentNote[], noteTypes: SkipAPIAgentNoteType[]}> {
|
|
1155
1425
|
try {
|
|
1156
1426
|
// if already configured this does nothing, just makes sure we're configured
|
|
1157
1427
|
await AIEngine.Instance.Config(false, contextUser);
|
|
@@ -1175,6 +1445,12 @@ cycle.`);
|
|
|
1175
1445
|
}
|
|
1176
1446
|
});
|
|
1177
1447
|
|
|
1448
|
+
if (filterUserNotesToContextUser){
|
|
1449
|
+
// filter out any notes that are not for this user
|
|
1450
|
+
notes = notes.filter((n) => n.type === 'Global' ||
|
|
1451
|
+
(n.type === 'User' && n.userId === contextUser.ID));
|
|
1452
|
+
}
|
|
1453
|
+
|
|
1178
1454
|
noteTypes = AIEngine.Instance.AgentNoteTypes.map((r) => {
|
|
1179
1455
|
return {
|
|
1180
1456
|
id: r.ID,
|
|
@@ -1197,6 +1473,14 @@ cycle.`);
|
|
|
1197
1473
|
}
|
|
1198
1474
|
}
|
|
1199
1475
|
|
|
1476
|
+
/**
|
|
1477
|
+
* Packs entity rows for inclusion in Skip requests
|
|
1478
|
+
* Provides sample data based on entity configuration
|
|
1479
|
+
*
|
|
1480
|
+
* @param e Entity information
|
|
1481
|
+
* @param dataSource Database connection
|
|
1482
|
+
* @returns Array of entity rows based on packing configuration
|
|
1483
|
+
*/
|
|
1200
1484
|
protected async PackEntityRows(e: EntityInfo, dataSource: DataSource): Promise<any[]> {
|
|
1201
1485
|
try {
|
|
1202
1486
|
if (e.RowsToPackWithSchema === 'None')
|
|
@@ -1249,6 +1533,14 @@ cycle.`);
|
|
|
1249
1533
|
}
|
|
1250
1534
|
}
|
|
1251
1535
|
|
|
1536
|
+
/**
|
|
1537
|
+
* Packs possible values for an entity field
|
|
1538
|
+
* These values help Skip understand the domain and valid values for fields
|
|
1539
|
+
*
|
|
1540
|
+
* @param f Field information
|
|
1541
|
+
* @param dataSource Database connection
|
|
1542
|
+
* @returns Array of possible values for the field
|
|
1543
|
+
*/
|
|
1252
1544
|
protected async PackFieldPossibleValues(f: EntityFieldInfo, dataSource: DataSource): Promise<SkipEntityFieldValueInfo[]> {
|
|
1253
1545
|
try {
|
|
1254
1546
|
if (f.ValuesToPackWithSchema === 'None') {
|
|
@@ -1291,6 +1583,14 @@ cycle.`);
|
|
|
1291
1583
|
}
|
|
1292
1584
|
}
|
|
1293
1585
|
|
|
1586
|
+
/**
|
|
1587
|
+
* Gets distinct values for a field from the database
|
|
1588
|
+
* Used to provide Skip with information about the possible values
|
|
1589
|
+
*
|
|
1590
|
+
* @param f Field information
|
|
1591
|
+
* @param dataSource Database connection
|
|
1592
|
+
* @returns Array of distinct values for the field
|
|
1593
|
+
*/
|
|
1294
1594
|
protected async GetFieldDistinctValues(f: EntityFieldInfo, dataSource: DataSource): Promise<SkipEntityFieldValueInfo[]> {
|
|
1295
1595
|
try {
|
|
1296
1596
|
const sql = `SELECT DISTINCT ${f.Name} FROM ${f.SchemaName}.${f.BaseView}`;
|
|
@@ -1319,10 +1619,17 @@ cycle.`);
|
|
|
1319
1619
|
private static __skipEntitiesCache$: BehaviorSubject<Promise<SkipEntityInfo[]> | null> = new BehaviorSubject<Promise<SkipEntityInfo[]> | null>(null);
|
|
1320
1620
|
private static __lastRefreshTime: number = 0;
|
|
1321
1621
|
|
|
1622
|
+
/**
|
|
1623
|
+
* Refreshes the Skip entities cache
|
|
1624
|
+
* Rebuilds the entity information that is provided to Skip
|
|
1625
|
+
*
|
|
1626
|
+
* @param dataSource Database connection
|
|
1627
|
+
* @returns Updated array of entity information
|
|
1628
|
+
*/
|
|
1322
1629
|
private async refreshSkipEntities(dataSource: DataSource): Promise<SkipEntityInfo[]> {
|
|
1323
1630
|
try {
|
|
1324
1631
|
const md = new Metadata();
|
|
1325
|
-
const skipSpecialIncludeEntities = (configInfo.askSkip?.
|
|
1632
|
+
const skipSpecialIncludeEntities = (configInfo.askSkip?.entitiesToSend?.includeEntitiesFromExcludedSchemas ?? [])
|
|
1326
1633
|
.map((e) => e.trim().toLowerCase());
|
|
1327
1634
|
|
|
1328
1635
|
// get the list of entities
|
|
@@ -1352,6 +1659,15 @@ cycle.`);
|
|
|
1352
1659
|
}
|
|
1353
1660
|
}
|
|
1354
1661
|
|
|
1662
|
+
/**
|
|
1663
|
+
* Builds or retrieves Skip entities from cache
|
|
1664
|
+
* Uses caching to avoid expensive rebuilding of entity information
|
|
1665
|
+
*
|
|
1666
|
+
* @param dataSource Database connection
|
|
1667
|
+
* @param forceRefresh Whether to force a refresh regardless of cache state
|
|
1668
|
+
* @param refreshIntervalMinutes Minutes before cache expires
|
|
1669
|
+
* @returns Array of entity information
|
|
1670
|
+
*/
|
|
1355
1671
|
public async BuildSkipEntities(dataSource: DataSource, forceRefresh: boolean = false, refreshIntervalMinutes: number = 15): Promise<SkipEntityInfo[]> {
|
|
1356
1672
|
try {
|
|
1357
1673
|
const now = Date.now();
|
|
@@ -1372,6 +1688,14 @@ cycle.`);
|
|
|
1372
1688
|
}
|
|
1373
1689
|
}
|
|
1374
1690
|
|
|
1691
|
+
/**
|
|
1692
|
+
* Packs information about a single entity for Skip
|
|
1693
|
+
* Includes fields, relationships, and sample data
|
|
1694
|
+
*
|
|
1695
|
+
* @param e Entity information
|
|
1696
|
+
* @param dataSource Database connection
|
|
1697
|
+
* @returns Packaged entity information
|
|
1698
|
+
*/
|
|
1375
1699
|
protected async PackSingleSkipEntityInfo(e: EntityInfo, dataSource: DataSource): Promise<SkipEntityInfo> {
|
|
1376
1700
|
try {
|
|
1377
1701
|
const ret: SkipEntityInfo = {
|
|
@@ -1405,6 +1729,13 @@ cycle.`);
|
|
|
1405
1729
|
}
|
|
1406
1730
|
}
|
|
1407
1731
|
|
|
1732
|
+
/**
|
|
1733
|
+
* Packs information about a single entity relationship
|
|
1734
|
+
* These relationships help Skip understand the data model
|
|
1735
|
+
*
|
|
1736
|
+
* @param r Relationship information
|
|
1737
|
+
* @returns Packaged relationship information
|
|
1738
|
+
*/
|
|
1408
1739
|
protected PackSingleSkipEntityRelationship(r: EntityRelationshipInfo): SkipEntityRelationshipInfo {
|
|
1409
1740
|
try {
|
|
1410
1741
|
return {
|
|
@@ -1428,6 +1759,14 @@ cycle.`);
|
|
|
1428
1759
|
}
|
|
1429
1760
|
}
|
|
1430
1761
|
|
|
1762
|
+
/**
|
|
1763
|
+
* Packs information about a single entity field
|
|
1764
|
+
* Includes metadata and possible values
|
|
1765
|
+
*
|
|
1766
|
+
* @param f Field information
|
|
1767
|
+
* @param dataSource Database connection
|
|
1768
|
+
* @returns Packaged field information
|
|
1769
|
+
*/
|
|
1431
1770
|
protected async PackSingleSkipEntityField(f: EntityFieldInfo, dataSource: DataSource): Promise<SkipEntityFieldInfo> {
|
|
1432
1771
|
try {
|
|
1433
1772
|
return {
|
|
@@ -1468,6 +1807,19 @@ cycle.`);
|
|
|
1468
1807
|
}
|
|
1469
1808
|
}
|
|
1470
1809
|
|
|
1810
|
+
/**
|
|
1811
|
+
* Handles initial object loading for Skip chat interactions
|
|
1812
|
+
* Creates or loads conversation objects, data contexts, and other required entities
|
|
1813
|
+
*
|
|
1814
|
+
* @param dataSource Database connection
|
|
1815
|
+
* @param ConversationId ID of an existing conversation, or empty for a new one
|
|
1816
|
+
* @param UserQuestion The user's question or message
|
|
1817
|
+
* @param user User information
|
|
1818
|
+
* @param userPayload User payload from context
|
|
1819
|
+
* @param md Metadata instance
|
|
1820
|
+
* @param DataContextId Optional ID of a data context to use
|
|
1821
|
+
* @returns Object containing loaded entities and contexts
|
|
1822
|
+
*/
|
|
1471
1823
|
protected async HandleSkipChatInitialObjectLoading(
|
|
1472
1824
|
dataSource: DataSource,
|
|
1473
1825
|
ConversationId: string,
|
|
@@ -1593,6 +1945,15 @@ cycle.`);
|
|
|
1593
1945
|
return { dataContext, convoEntity, dataContextEntity, convoDetailEntity };
|
|
1594
1946
|
}
|
|
1595
1947
|
|
|
1948
|
+
/**
|
|
1949
|
+
* Loads conversation details from the database and transforms them into Skip message format
|
|
1950
|
+
* Used to provide Skip with conversation history for context
|
|
1951
|
+
*
|
|
1952
|
+
* @param dataSource Database connection
|
|
1953
|
+
* @param ConversationId ID of the conversation to load details for
|
|
1954
|
+
* @param maxHistoricalMessages Maximum number of historical messages to include
|
|
1955
|
+
* @returns Array of messages in Skip format
|
|
1956
|
+
*/
|
|
1596
1957
|
protected async LoadConversationDetailsIntoSkipMessages(
|
|
1597
1958
|
dataSource: DataSource,
|
|
1598
1959
|
ConversationId: string,
|
|
@@ -1684,6 +2045,13 @@ cycle.`);
|
|
|
1684
2045
|
}
|
|
1685
2046
|
}
|
|
1686
2047
|
|
|
2048
|
+
/**
|
|
2049
|
+
* Maps database role values to Skip API role format
|
|
2050
|
+
* Converts role names from database format to the format expected by Skip API
|
|
2051
|
+
*
|
|
2052
|
+
* @param role Database role value
|
|
2053
|
+
* @returns Skip API role value ('user' or 'system')
|
|
2054
|
+
*/
|
|
1687
2055
|
protected MapDBRoleToSkipRole(role: string): 'user' | 'system' {
|
|
1688
2056
|
switch (role.trim().toLowerCase()) {
|
|
1689
2057
|
case 'ai':
|
|
@@ -1695,6 +2063,25 @@ cycle.`);
|
|
|
1695
2063
|
}
|
|
1696
2064
|
}
|
|
1697
2065
|
|
|
2066
|
+
/**
|
|
2067
|
+
* Handles the main Skip chat request processing flow
|
|
2068
|
+
* Routes the request through the different phases based on the Skip API response
|
|
2069
|
+
*
|
|
2070
|
+
* @param input Skip API request to send
|
|
2071
|
+
* @param UserQuestion The question or message from the user
|
|
2072
|
+
* @param user User information
|
|
2073
|
+
* @param dataSource Database connection
|
|
2074
|
+
* @param ConversationId ID of the conversation
|
|
2075
|
+
* @param userPayload User payload from context
|
|
2076
|
+
* @param pubSub Publisher/subscriber for events
|
|
2077
|
+
* @param md Metadata instance
|
|
2078
|
+
* @param convoEntity Conversation entity
|
|
2079
|
+
* @param convoDetailEntity Conversation detail entity for the user message
|
|
2080
|
+
* @param dataContext Data context associated with the conversation
|
|
2081
|
+
* @param dataContextEntity Data context entity
|
|
2082
|
+
* @param conversationDetailCount Tracking count to prevent infinite loops
|
|
2083
|
+
* @returns Result of the Skip interaction
|
|
2084
|
+
*/
|
|
1698
2085
|
protected async HandleSkipChatRequest(
|
|
1699
2086
|
input: SkipAPIRequest,
|
|
1700
2087
|
UserQuestion: string,
|
|
@@ -1710,7 +2097,8 @@ cycle.`);
|
|
|
1710
2097
|
dataContextEntity: DataContextEntity,
|
|
1711
2098
|
conversationDetailCount: number
|
|
1712
2099
|
): Promise<AskSkipResultType> {
|
|
1713
|
-
|
|
2100
|
+
const skipConfigInfo = configInfo.askSkip;
|
|
2101
|
+
LogStatus(` >>> HandleSkipRequest: Sending request to Skip API: ${skipConfigInfo.chatURL}`);
|
|
1714
2102
|
|
|
1715
2103
|
if (conversationDetailCount > 10) {
|
|
1716
2104
|
// At this point it is likely that we are stuck in a loop, so we stop here
|
|
@@ -1736,7 +2124,7 @@ cycle.`);
|
|
|
1736
2124
|
}
|
|
1737
2125
|
|
|
1738
2126
|
const response = await sendPostRequest(
|
|
1739
|
-
|
|
2127
|
+
skipConfigInfo.chatURL,
|
|
1740
2128
|
input,
|
|
1741
2129
|
true,
|
|
1742
2130
|
null,
|
|
@@ -1849,6 +2237,15 @@ cycle.`);
|
|
|
1849
2237
|
}
|
|
1850
2238
|
}
|
|
1851
2239
|
|
|
2240
|
+
/**
|
|
2241
|
+
* Publishes a status update message to the user based on the Skip API response
|
|
2242
|
+
* Provides feedback about what phase of processing is happening
|
|
2243
|
+
*
|
|
2244
|
+
* @param apiResponse The response from the Skip API
|
|
2245
|
+
* @param userPayload User payload from context
|
|
2246
|
+
* @param conversationID ID of the conversation
|
|
2247
|
+
* @param pubSub Publisher/subscriber for events
|
|
2248
|
+
*/
|
|
1852
2249
|
protected async PublishApiResponseUserUpdateMessage(
|
|
1853
2250
|
apiResponse: SkipAPIResponse,
|
|
1854
2251
|
userPayload: UserPayload,
|
|
@@ -1881,6 +2278,24 @@ cycle.`);
|
|
|
1881
2278
|
});
|
|
1882
2279
|
}
|
|
1883
2280
|
|
|
2281
|
+
/**
|
|
2282
|
+
* Handles the analysis complete phase of the Skip chat process
|
|
2283
|
+
* Finalizes the conversation and creates necessary artifacts
|
|
2284
|
+
*
|
|
2285
|
+
* @param apiRequest The original request sent to Skip
|
|
2286
|
+
* @param apiResponse The analysis complete response from Skip
|
|
2287
|
+
* @param UserQuestion The original user question
|
|
2288
|
+
* @param user User information
|
|
2289
|
+
* @param dataSource Database connection
|
|
2290
|
+
* @param ConversationId ID of the conversation
|
|
2291
|
+
* @param userPayload User payload from context
|
|
2292
|
+
* @param pubSub Publisher/subscriber for events
|
|
2293
|
+
* @param convoEntity Conversation entity
|
|
2294
|
+
* @param convoDetailEntity Conversation detail entity for the user message
|
|
2295
|
+
* @param dataContext Data context associated with the conversation
|
|
2296
|
+
* @param dataContextEntity Data context entity
|
|
2297
|
+
* @returns Result of the Skip interaction
|
|
2298
|
+
*/
|
|
1884
2299
|
protected async HandleAnalysisComplete(
|
|
1885
2300
|
apiRequest: SkipAPIRequest,
|
|
1886
2301
|
apiResponse: SkipAPIAnalysisCompleteResponse,
|
|
@@ -1927,6 +2342,22 @@ cycle.`);
|
|
|
1927
2342
|
return response;
|
|
1928
2343
|
}
|
|
1929
2344
|
|
|
2345
|
+
/**
|
|
2346
|
+
* Handles the clarifying question phase of the Skip chat process
|
|
2347
|
+
* Creates a conversation detail for the clarifying question from Skip
|
|
2348
|
+
*
|
|
2349
|
+
* @param apiRequest The original request sent to Skip
|
|
2350
|
+
* @param apiResponse The clarifying question response from Skip
|
|
2351
|
+
* @param UserQuestion The original user question
|
|
2352
|
+
* @param user User information
|
|
2353
|
+
* @param dataSource Database connection
|
|
2354
|
+
* @param ConversationId ID of the conversation
|
|
2355
|
+
* @param userPayload User payload from context
|
|
2356
|
+
* @param pubSub Publisher/subscriber for events
|
|
2357
|
+
* @param convoEntity Conversation entity
|
|
2358
|
+
* @param convoDetailEntity Conversation detail entity for the user message
|
|
2359
|
+
* @returns Result of the Skip interaction
|
|
2360
|
+
*/
|
|
1930
2361
|
protected async HandleClarifyingQuestionPhase(
|
|
1931
2362
|
apiRequest: SkipAPIRequest,
|
|
1932
2363
|
apiResponse: SkipAPIClarifyingQuestionResponse,
|
|
@@ -1975,6 +2406,25 @@ cycle.`);
|
|
|
1975
2406
|
}
|
|
1976
2407
|
}
|
|
1977
2408
|
|
|
2409
|
+
/**
|
|
2410
|
+
* Handles the data request phase of the Skip chat process
|
|
2411
|
+
* Processes data requests from Skip and loads requested data
|
|
2412
|
+
*
|
|
2413
|
+
* @param apiRequest The original request sent to Skip
|
|
2414
|
+
* @param apiResponse The data request response from Skip
|
|
2415
|
+
* @param UserQuestion The original user question
|
|
2416
|
+
* @param user User information
|
|
2417
|
+
* @param dataSource Database connection
|
|
2418
|
+
* @param ConversationId ID of the conversation
|
|
2419
|
+
* @param userPayload User payload from context
|
|
2420
|
+
* @param pubSub Publisher/subscriber for events
|
|
2421
|
+
* @param convoEntity Conversation entity
|
|
2422
|
+
* @param convoDetailEntity Conversation detail entity for the user message
|
|
2423
|
+
* @param dataContext Data context associated with the conversation
|
|
2424
|
+
* @param dataContextEntity Data context entity
|
|
2425
|
+
* @param conversationDetailCount Tracking count to prevent infinite loops
|
|
2426
|
+
* @returns Result of the Skip interaction
|
|
2427
|
+
*/
|
|
1978
2428
|
protected async HandleDataRequestPhase(
|
|
1979
2429
|
apiRequest: SkipAPIRequest,
|
|
1980
2430
|
apiResponse: SkipAPIDataRequestResponse,
|
|
@@ -2126,14 +2576,19 @@ cycle.`);
|
|
|
2126
2576
|
}
|
|
2127
2577
|
|
|
2128
2578
|
/**
|
|
2129
|
-
*
|
|
2130
|
-
*
|
|
2131
|
-
*
|
|
2132
|
-
* @param
|
|
2133
|
-
* @param
|
|
2134
|
-
* @param
|
|
2135
|
-
* @param
|
|
2136
|
-
* @
|
|
2579
|
+
* Finishes a successful conversation and notifies the user
|
|
2580
|
+
* Creates necessary records, artifacts, and notifications
|
|
2581
|
+
*
|
|
2582
|
+
* @param apiResponse The analysis complete response from Skip
|
|
2583
|
+
* @param dataContext Data context associated with the conversation
|
|
2584
|
+
* @param dataContextEntity Data context entity
|
|
2585
|
+
* @param md Metadata instance
|
|
2586
|
+
* @param user User information
|
|
2587
|
+
* @param convoEntity Conversation entity
|
|
2588
|
+
* @param pubSub Publisher/subscriber for events
|
|
2589
|
+
* @param userPayload User payload from context
|
|
2590
|
+
* @param dataSource Database connection
|
|
2591
|
+
* @returns The ID of the AI message conversation detail
|
|
2137
2592
|
*/
|
|
2138
2593
|
protected async FinishConversationAndNotifyUser(
|
|
2139
2594
|
apiResponse: SkipAPIAnalysisCompleteResponse,
|
|
@@ -2294,18 +2749,34 @@ cycle.`);
|
|
|
2294
2749
|
};
|
|
2295
2750
|
}
|
|
2296
2751
|
|
|
2297
|
-
|
|
2752
|
+
/**
|
|
2753
|
+
* Gets the ID of an agent note type by its name
|
|
2754
|
+
* Falls back to a default note type if the specified one is not found
|
|
2755
|
+
*
|
|
2756
|
+
* @param name Name of the agent note type
|
|
2757
|
+
* @param defaultNoteType Default note type to use if the specified one is not found
|
|
2758
|
+
* @returns ID of the agent note type
|
|
2759
|
+
*/
|
|
2760
|
+
protected getAgentNoteTypeIDByName(name: string, defaultNoteType: string = 'AI'): string {
|
|
2298
2761
|
const noteTypeID = AIEngine.Instance.AgentNoteTypes.find(nt => nt.Name.trim().toLowerCase() === name.trim().toLowerCase())?.ID;
|
|
2299
2762
|
if (noteTypeID) {
|
|
2300
2763
|
return noteTypeID;
|
|
2301
2764
|
}
|
|
2302
2765
|
else{
|
|
2303
|
-
// default
|
|
2304
|
-
const
|
|
2305
|
-
return
|
|
2766
|
+
// default
|
|
2767
|
+
const defaultNoteTypeID = AIEngine.Instance.AgentNoteTypes.find(nt => nt.Name.trim().toLowerCase() === defaultNoteType.trim().toLowerCase())?.ID;
|
|
2768
|
+
return defaultNoteTypeID;
|
|
2306
2769
|
}
|
|
2307
2770
|
}
|
|
2308
2771
|
|
|
2772
|
+
/**
|
|
2773
|
+
* Gets data from a view
|
|
2774
|
+
* Helper method to run a view and retrieve its data
|
|
2775
|
+
*
|
|
2776
|
+
* @param ViewId ID of the view to run
|
|
2777
|
+
* @param user User context for the query
|
|
2778
|
+
* @returns Results of the view query
|
|
2779
|
+
*/
|
|
2309
2780
|
protected async getViewData(ViewId: string, user: UserInfo): Promise<any> {
|
|
2310
2781
|
const rv = new RunView();
|
|
2311
2782
|
const result = await rv.RunView({ ViewID: ViewId, IgnoreMaxRows: true }, user);
|
|
@@ -2314,8 +2785,11 @@ cycle.`);
|
|
|
2314
2785
|
}
|
|
2315
2786
|
|
|
2316
2787
|
/**
|
|
2317
|
-
* Manually executes the Skip AI learning cycle
|
|
2788
|
+
* Manually executes the Skip AI learning cycle
|
|
2789
|
+
* Allows triggering a learning cycle on demand rather than waiting for scheduled execution
|
|
2790
|
+
*
|
|
2318
2791
|
* @param OrganizationId Optional organization ID to register for this run
|
|
2792
|
+
* @returns Result of the manual learning cycle execution
|
|
2319
2793
|
*/
|
|
2320
2794
|
@Mutation(() => ManualLearningCycleResultType)
|
|
2321
2795
|
async ManuallyExecuteSkipLearningCycle(
|
|
@@ -2323,17 +2797,17 @@ cycle.`);
|
|
|
2323
2797
|
): Promise<ManualLearningCycleResultType> {
|
|
2324
2798
|
try {
|
|
2325
2799
|
LogStatus('Manual execution of Skip learning cycle requested via API');
|
|
2326
|
-
|
|
2800
|
+
const skipConfigInfo = configInfo.askSkip;
|
|
2327
2801
|
// First check if learning cycles are enabled in configuration
|
|
2328
|
-
if (
|
|
2802
|
+
if (!skipConfigInfo.learningCycleEnabled) {
|
|
2329
2803
|
return {
|
|
2330
2804
|
Success: false,
|
|
2331
|
-
Message: 'Learning cycles are
|
|
2805
|
+
Message: 'Learning cycles are not enabled in configuration'
|
|
2332
2806
|
};
|
|
2333
2807
|
}
|
|
2334
2808
|
|
|
2335
2809
|
// Check if we have a valid endpoint when cycles are enabled
|
|
2336
|
-
if (!
|
|
2810
|
+
if (!skipConfigInfo.learningCycleURL || skipConfigInfo.learningCycleURL.trim().length === 0) {
|
|
2337
2811
|
return {
|
|
2338
2812
|
Success: false,
|
|
2339
2813
|
Message: 'Learning cycle API endpoint is not configured'
|
|
@@ -2341,7 +2815,7 @@ cycle.`);
|
|
|
2341
2815
|
}
|
|
2342
2816
|
|
|
2343
2817
|
// Use the organization ID from config if not provided
|
|
2344
|
-
const orgId = OrganizationId ||
|
|
2818
|
+
const orgId = OrganizationId || skipConfigInfo.orgID;
|
|
2345
2819
|
|
|
2346
2820
|
// Call the scheduler's manual execution method with org ID
|
|
2347
2821
|
const result = await LearningCycleScheduler.Instance.manuallyExecuteLearningCycle(orgId);
|
|
@@ -2364,6 +2838,9 @@ cycle.`);
|
|
|
2364
2838
|
|
|
2365
2839
|
/**
|
|
2366
2840
|
* Gets the current status of the learning cycle scheduler
|
|
2841
|
+
* Provides information about the scheduler state and any running cycles
|
|
2842
|
+
*
|
|
2843
|
+
* @returns Status information about the learning cycle scheduler
|
|
2367
2844
|
*/
|
|
2368
2845
|
@Query(() => LearningCycleStatusType)
|
|
2369
2846
|
async GetLearningCycleStatus(): Promise<LearningCycleStatusType> {
|
|
@@ -2393,15 +2870,19 @@ cycle.`);
|
|
|
2393
2870
|
|
|
2394
2871
|
/**
|
|
2395
2872
|
* Checks if a specific organization is running a learning cycle
|
|
2873
|
+
* Used to determine if a new learning cycle can be started for an organization
|
|
2874
|
+
*
|
|
2396
2875
|
* @param OrganizationId The organization ID to check
|
|
2876
|
+
* @returns Information about the running cycle, or null if no cycle is running
|
|
2397
2877
|
*/
|
|
2398
2878
|
@Query(() => RunningOrganizationType, { nullable: true })
|
|
2399
2879
|
async IsOrganizationRunningLearningCycle(
|
|
2400
2880
|
@Arg('OrganizationId', () => String) OrganizationId: string
|
|
2401
2881
|
): Promise<RunningOrganizationType | null> {
|
|
2402
2882
|
try {
|
|
2883
|
+
const skipConfigInfo = configInfo.askSkip;
|
|
2403
2884
|
// Use the organization ID from config if not provided
|
|
2404
|
-
const orgId = OrganizationId ||
|
|
2885
|
+
const orgId = OrganizationId || skipConfigInfo.orgID;
|
|
2405
2886
|
|
|
2406
2887
|
const status = LearningCycleScheduler.Instance.isOrganizationRunningCycle(orgId);
|
|
2407
2888
|
|
|
@@ -2424,7 +2905,10 @@ cycle.`);
|
|
|
2424
2905
|
|
|
2425
2906
|
/**
|
|
2426
2907
|
* Stops a running learning cycle for a specific organization
|
|
2908
|
+
* Allows manual intervention to stop a learning cycle that is taking too long or causing issues
|
|
2909
|
+
*
|
|
2427
2910
|
* @param OrganizationId The organization ID to stop the cycle for
|
|
2911
|
+
* @returns Result of the stop operation, including details about the stopped cycle
|
|
2428
2912
|
*/
|
|
2429
2913
|
@Mutation(() => StopLearningCycleResultType)
|
|
2430
2914
|
async StopLearningCycleForOrganization(
|
|
@@ -2432,7 +2916,7 @@ cycle.`);
|
|
|
2432
2916
|
): Promise<StopLearningCycleResultType> {
|
|
2433
2917
|
try {
|
|
2434
2918
|
// Use the organization ID from config if not provided
|
|
2435
|
-
const orgId = OrganizationId ||
|
|
2919
|
+
const orgId = OrganizationId || configInfo.askSkip.orgID;
|
|
2436
2920
|
|
|
2437
2921
|
const result = LearningCycleScheduler.Instance.stopLearningCycleForOrganization(orgId);
|
|
2438
2922
|
|