wingbot 3.71.6 → 3.72.1
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/.github/workflows/deploy.yml +2 -2
- package/.github/workflows/pullRequest.yml +2 -2
- package/package.json +4 -2
- package/src/Ai.js +44 -3
- package/src/AiMatching.js +66 -40
- package/src/BotAppSender.js +6 -4
- package/src/BuildRouter.js +2 -0
- package/src/LLM.js +183 -2
- package/src/LLMSession.js +90 -3
- package/src/Processor.js +1 -1
- package/src/Responder.js +143 -3
- package/src/ReturnSender.js +4 -4
- package/src/Tester.js +44 -2
- package/src/graphApi/GraphApi.js +31 -1
- package/src/graphApi/gqlRules.js +87 -0
- package/src/graphApi/schema.gql +15 -0
- package/src/resolvers/contextMessage.js +21 -4
- package/src/resolvers/message.js +43 -1
- package/src/resolvers/utils.js +14 -5
- package/src/testTools/PromptAssert.js +184 -0
- package/src/testTools/asserts.js +42 -4
- package/src/utils/customCondition.js +14 -1
- package/src/utils/getCondition.js +16 -4
- package/src/utils/getUpdate.js +4 -4
- package/src/utils/stateData.js +2 -0
package/src/LLMSession.js
CHANGED
|
@@ -13,6 +13,8 @@ const LLM = require('./LLM');
|
|
|
13
13
|
/** @typedef {'tool'} LLMToolRole */
|
|
14
14
|
/** @typedef {LLMChatRole|LLMSystemRole|LLMToolRole|string} LLMRole */
|
|
15
15
|
|
|
16
|
+
/** @typedef {LLMRole|'conversation'} FilterScope */
|
|
17
|
+
|
|
16
18
|
/** @typedef {'stop'|'length'|'tool_calls'|'content_filter'} LLMFinishReason */
|
|
17
19
|
|
|
18
20
|
/**
|
|
@@ -38,6 +40,19 @@ const LLM = require('./LLM');
|
|
|
38
40
|
* @param {QuickReply[]} quickReplies
|
|
39
41
|
*/
|
|
40
42
|
|
|
43
|
+
/**
|
|
44
|
+
* @callback LLMFilterFn
|
|
45
|
+
* @param {string} text
|
|
46
|
+
* @param {LLMRole} role
|
|
47
|
+
* @returns {boolean|string}
|
|
48
|
+
*/
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* @typedef {object} LLMFilter
|
|
52
|
+
* @prop {LLMFilterFn} filter
|
|
53
|
+
* @prop {FilterScope} scope
|
|
54
|
+
*/
|
|
55
|
+
|
|
41
56
|
/**
|
|
42
57
|
* @class LLMSession
|
|
43
58
|
*/
|
|
@@ -48,8 +63,9 @@ class LLMSession {
|
|
|
48
63
|
* @param {LLM} llm
|
|
49
64
|
* @param {LLMMessage<any>[]} [chat]
|
|
50
65
|
* @param {SendCallback} [onSend]
|
|
66
|
+
* @param {LLMFilter[]} [filters=[]]
|
|
51
67
|
*/
|
|
52
|
-
constructor (llm, chat = [], onSend = () => {}) {
|
|
68
|
+
constructor (llm, chat = [], onSend = () => {}, filters = []) {
|
|
53
69
|
this._llm = llm;
|
|
54
70
|
|
|
55
71
|
this._onSend = onSend;
|
|
@@ -60,6 +76,13 @@ class LLMSession {
|
|
|
60
76
|
this._generatedIndex = null;
|
|
61
77
|
|
|
62
78
|
this._sort();
|
|
79
|
+
|
|
80
|
+
/** @type {LLMFilter[]} */
|
|
81
|
+
this._filters = filters;
|
|
82
|
+
|
|
83
|
+
this._SCOPE_CONVERSATION_ROLES = [
|
|
84
|
+
LLM.ROLE_ASSISTANT, LLM.ROLE_USER
|
|
85
|
+
];
|
|
63
86
|
}
|
|
64
87
|
|
|
65
88
|
_sort (what = this._chat) {
|
|
@@ -114,10 +137,43 @@ class LLMSession {
|
|
|
114
137
|
}
|
|
115
138
|
|
|
116
139
|
/**
|
|
140
|
+
*
|
|
141
|
+
* @param {boolean} [filtered=false]
|
|
117
142
|
* @returns {LLMMessage[]}
|
|
118
143
|
*/
|
|
119
|
-
toArray () {
|
|
120
|
-
|
|
144
|
+
toArray (filtered = false) {
|
|
145
|
+
const messages = this._mergeSystem();
|
|
146
|
+
if (!filtered || this._filters.length === 0) {
|
|
147
|
+
return messages;
|
|
148
|
+
}
|
|
149
|
+
return messages
|
|
150
|
+
.map((message) => {
|
|
151
|
+
if (!message.content) {
|
|
152
|
+
return message;
|
|
153
|
+
}
|
|
154
|
+
const content = this._filters.reduce((text, filter) => {
|
|
155
|
+
if (!text) {
|
|
156
|
+
return text;
|
|
157
|
+
}
|
|
158
|
+
if (filter.scope !== message.role
|
|
159
|
+
&& (filter.scope !== LLM.FILTER_SCOPE_CONVERSATION
|
|
160
|
+
|| !this._SCOPE_CONVERSATION_ROLES.includes(message.role))) {
|
|
161
|
+
return text;
|
|
162
|
+
}
|
|
163
|
+
const res = filter.filter(text, message.role);
|
|
164
|
+
return res === true ? text : res;
|
|
165
|
+
}, message.content);
|
|
166
|
+
|
|
167
|
+
if (!content) {
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return {
|
|
172
|
+
...message,
|
|
173
|
+
content
|
|
174
|
+
};
|
|
175
|
+
})
|
|
176
|
+
.filter((message) => message !== null);
|
|
121
177
|
}
|
|
122
178
|
|
|
123
179
|
/**
|
|
@@ -200,6 +256,20 @@ class LLMSession {
|
|
|
200
256
|
return this;
|
|
201
257
|
}
|
|
202
258
|
|
|
259
|
+
/**
|
|
260
|
+
*
|
|
261
|
+
* @param {LLMFilter|LLMFilter[]} filter
|
|
262
|
+
* @returns {this}
|
|
263
|
+
*/
|
|
264
|
+
addFilter (filter) {
|
|
265
|
+
if (Array.isArray(filter)) {
|
|
266
|
+
this._filters.push(...filter);
|
|
267
|
+
} else {
|
|
268
|
+
this._filters.push(filter);
|
|
269
|
+
}
|
|
270
|
+
return this;
|
|
271
|
+
}
|
|
272
|
+
|
|
203
273
|
/**
|
|
204
274
|
*
|
|
205
275
|
* @param {LLMProviderOptions} [options={}]
|
|
@@ -214,6 +284,23 @@ class LLMSession {
|
|
|
214
284
|
return result;
|
|
215
285
|
}
|
|
216
286
|
|
|
287
|
+
/**
|
|
288
|
+
*
|
|
289
|
+
* @returns {string}
|
|
290
|
+
*/
|
|
291
|
+
lastResponse () {
|
|
292
|
+
const messages = [];
|
|
293
|
+
for (let i = this._chat.length - 1; i >= 0; i--) {
|
|
294
|
+
const message = this._chat[i];
|
|
295
|
+
|
|
296
|
+
if (message.role !== LLM.ROLE_ASSISTANT || !message.content) {
|
|
297
|
+
break;
|
|
298
|
+
}
|
|
299
|
+
messages.unshift(message.content);
|
|
300
|
+
}
|
|
301
|
+
return messages.join('\n\n');
|
|
302
|
+
}
|
|
303
|
+
|
|
217
304
|
/**
|
|
218
305
|
*
|
|
219
306
|
* @param {boolean} [dontMarkAsSent=false]
|
package/src/Processor.js
CHANGED
|
@@ -416,7 +416,7 @@ class Processor extends EventEmitter {
|
|
|
416
416
|
});
|
|
417
417
|
}
|
|
418
418
|
|
|
419
|
-
const llm = new LLM(llmOptions);
|
|
419
|
+
const llm = new LLM(llmOptions, Ai.ai);
|
|
420
420
|
|
|
421
421
|
const result = await this
|
|
422
422
|
._dispatch(message, pageId, messageSender, responderData, preloadPromise, llm);
|
package/src/Responder.js
CHANGED
|
@@ -34,7 +34,14 @@ const EXCEPTION_HOPCOUNT_THRESHOLD = 5;
|
|
|
34
34
|
/** @typedef {import('./analytics/consts').TrackingType} TrackingType */
|
|
35
35
|
|
|
36
36
|
/** @typedef {import('./LLM').LLMConfiguration} LLMConfiguration */
|
|
37
|
+
/** @typedef {import('./LLM').PreprocessedRule} PreprocessedRule */
|
|
38
|
+
/** @typedef {import('./LLM').EvaluationRuleAction} EvaluationRuleAction */
|
|
39
|
+
/** @typedef {import('./LLM').EvaluationResult} EvaluationResult */
|
|
37
40
|
/** @typedef {import('./LLMSession').LLMMessage} LLMMessage */
|
|
41
|
+
/** @typedef {import('./LLMSession').LLMFilterFn} LLMFilterFn */
|
|
42
|
+
/** @typedef {import('./LLMSession').LLMFilter} LLMFilter */
|
|
43
|
+
/** @typedef {import('./LLMSession').FilterScope} FilterScope */
|
|
44
|
+
/** @typedef {import('./utils/stateData').IStateRequest} IStateRequest */
|
|
38
45
|
|
|
39
46
|
/**
|
|
40
47
|
* @enum {string} ExpectedInput
|
|
@@ -243,6 +250,28 @@ class Responder {
|
|
|
243
250
|
this._llmContext = new Map([
|
|
244
251
|
[this.LLM_CTX_DEFAULT, []]
|
|
245
252
|
]);
|
|
253
|
+
|
|
254
|
+
/** @type {Map<string,PreprocessedRule[]>} */
|
|
255
|
+
this._llmResultRules = new Map([
|
|
256
|
+
[this.LLM_CTX_DEFAULT, []]
|
|
257
|
+
]);
|
|
258
|
+
|
|
259
|
+
/** @type {Map<string,LLMFilter[]>} */
|
|
260
|
+
this._llmFilters = new Map([
|
|
261
|
+
[this.LLM_CTX_DEFAULT, []],
|
|
262
|
+
[null, []]
|
|
263
|
+
]);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
*
|
|
268
|
+
* @deprecated use llmAddInstructions() instead
|
|
269
|
+
* @param {PromptSource} systemPrompt
|
|
270
|
+
* @param {string} [contextType]
|
|
271
|
+
* @returns {this}
|
|
272
|
+
*/
|
|
273
|
+
llmAddSystemPrompt (systemPrompt, contextType) {
|
|
274
|
+
return this.llmAddInstructions(systemPrompt, contextType);
|
|
246
275
|
}
|
|
247
276
|
|
|
248
277
|
/**
|
|
@@ -251,7 +280,7 @@ class Responder {
|
|
|
251
280
|
* @param {string} contextType
|
|
252
281
|
* @returns {this}
|
|
253
282
|
*/
|
|
254
|
-
|
|
283
|
+
llmAddInstructions (systemPrompt, contextType = this.LLM_CTX_DEFAULT) {
|
|
255
284
|
if (!systemPrompt) {
|
|
256
285
|
return this;
|
|
257
286
|
}
|
|
@@ -264,12 +293,109 @@ class Responder {
|
|
|
264
293
|
return this;
|
|
265
294
|
}
|
|
266
295
|
|
|
296
|
+
/**
|
|
297
|
+
*
|
|
298
|
+
* @param {LLMFilter|LLMFilterFn} filter
|
|
299
|
+
* @param {FilterScope} [scope]
|
|
300
|
+
* @param {string} [contextType]
|
|
301
|
+
* @returns {this}
|
|
302
|
+
*/
|
|
303
|
+
llmAddFilter (
|
|
304
|
+
filter,
|
|
305
|
+
scope = LLM.FILTER_SCOPE_CONVERSATION,
|
|
306
|
+
contextType = null
|
|
307
|
+
) {
|
|
308
|
+
/** @type {LLMFilter} */
|
|
309
|
+
const addFilter = typeof filter === 'function'
|
|
310
|
+
? {
|
|
311
|
+
filter,
|
|
312
|
+
scope
|
|
313
|
+
}
|
|
314
|
+
: filter;
|
|
315
|
+
|
|
316
|
+
if (!this._llmFilters.has(contextType)) {
|
|
317
|
+
this._llmFilters.set(contextType, []);
|
|
318
|
+
}
|
|
319
|
+
this._llmFilters.get(contextType).push(addFilter);
|
|
320
|
+
return this;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
*
|
|
325
|
+
* @param {string[]|PreprocessedRule} rule
|
|
326
|
+
* @param {EvaluationRuleAction} [action]
|
|
327
|
+
* @param {object} [setState]
|
|
328
|
+
* @param {string} [contextType]
|
|
329
|
+
* @returns {this}
|
|
330
|
+
*/
|
|
331
|
+
llmAddResultRule (
|
|
332
|
+
rule,
|
|
333
|
+
action = null,
|
|
334
|
+
setState = null,
|
|
335
|
+
contextType = this.LLM_CTX_DEFAULT
|
|
336
|
+
) {
|
|
337
|
+
let addRule = rule;
|
|
338
|
+
|
|
339
|
+
if (Array.isArray(addRule)) {
|
|
340
|
+
[addRule] = LLM.preprocessEvaluationRules([{
|
|
341
|
+
// @ts-ignore
|
|
342
|
+
aiTags: rule,
|
|
343
|
+
action,
|
|
344
|
+
setState
|
|
345
|
+
}], {
|
|
346
|
+
ai: this.llm.ai
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
if (!this._llmResultRules.has(contextType)) {
|
|
351
|
+
this._llmResultRules.set(contextType, []);
|
|
352
|
+
}
|
|
353
|
+
this._llmResultRules.get(contextType).push(addRule);
|
|
354
|
+
return this;
|
|
355
|
+
}
|
|
356
|
+
|
|
267
357
|
async llmSession (contextType = this.LLM_CTX_DEFAULT) {
|
|
268
358
|
const system = await this._getSystemContentForType(contextType);
|
|
269
359
|
|
|
270
360
|
const chat = system.map((content) => ({ role: LLM.ROLE_SYSTEM, content }));
|
|
271
361
|
|
|
272
|
-
|
|
362
|
+
const filters = this._filtersForContext(contextType);
|
|
363
|
+
return new LLMSession(this.llm, chat, this._llmSend.bind(this), filters);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
*
|
|
368
|
+
* @param {LLMSession} session
|
|
369
|
+
* @param {string} [contextType]
|
|
370
|
+
* @returns {Promise<EvaluationResult>}
|
|
371
|
+
*/
|
|
372
|
+
async llmEvaluate (session, contextType = this.LLM_CTX_DEFAULT) {
|
|
373
|
+
const rules = this._llmResultRules.get(contextType) || [];
|
|
374
|
+
const text = session.lastResponse();
|
|
375
|
+
|
|
376
|
+
if (rules.length === 0 || !text) {
|
|
377
|
+
return {
|
|
378
|
+
action: null,
|
|
379
|
+
setState: {},
|
|
380
|
+
results: [],
|
|
381
|
+
discard: false
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/** @type {IStateRequest} */
|
|
386
|
+
const req = {
|
|
387
|
+
state: this.options.state,
|
|
388
|
+
text: () => text,
|
|
389
|
+
senderId: this._senderId,
|
|
390
|
+
pageId: this._pageId,
|
|
391
|
+
actionData: () => this._data,
|
|
392
|
+
isConfidentInput: () => false
|
|
393
|
+
};
|
|
394
|
+
|
|
395
|
+
const result = await this.llm.evaluateResultWithRules(text, rules, req, this);
|
|
396
|
+
this.setState(result.setState);
|
|
397
|
+
|
|
398
|
+
return result;
|
|
273
399
|
}
|
|
274
400
|
|
|
275
401
|
async _replaceAsync (str, regex, asyncFn) {
|
|
@@ -339,7 +465,20 @@ class Responder {
|
|
|
339
465
|
...LLM.anonymizeTranscript(transcript, transcriptAnonymize)
|
|
340
466
|
];
|
|
341
467
|
|
|
342
|
-
|
|
468
|
+
const filters = this._filtersForContext(contextType);
|
|
469
|
+
return new LLMSession(this.llm, chat, this._llmSend.bind(this), filters);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
*
|
|
474
|
+
* @param {string|null} contextType
|
|
475
|
+
* @returns {LLMFilter[]}
|
|
476
|
+
*/
|
|
477
|
+
_filtersForContext (contextType) {
|
|
478
|
+
return [
|
|
479
|
+
...(this._llmFilters.get(contextType) || []),
|
|
480
|
+
...this._llmFilters.get(null)
|
|
481
|
+
];
|
|
343
482
|
}
|
|
344
483
|
|
|
345
484
|
/**
|
|
@@ -394,6 +533,7 @@ class Responder {
|
|
|
394
533
|
*/
|
|
395
534
|
setFlag (flag) {
|
|
396
535
|
this._senderMeta.flag = flag;
|
|
536
|
+
// @ts-ignore
|
|
397
537
|
return this;
|
|
398
538
|
}
|
|
399
539
|
|
package/src/ReturnSender.js
CHANGED
|
@@ -162,7 +162,7 @@ class ReturnSender {
|
|
|
162
162
|
};
|
|
163
163
|
|
|
164
164
|
/** @type {PromptInfo[]} */
|
|
165
|
-
this.
|
|
165
|
+
this.prompts = [];
|
|
166
166
|
|
|
167
167
|
this._responseTexts = [];
|
|
168
168
|
|
|
@@ -199,7 +199,7 @@ class ReturnSender {
|
|
|
199
199
|
* @param {PromptInfo} promptInfo
|
|
200
200
|
*/
|
|
201
201
|
logPrompt (promptInfo) {
|
|
202
|
-
this.
|
|
202
|
+
this.prompts.push(promptInfo);
|
|
203
203
|
}
|
|
204
204
|
|
|
205
205
|
/**
|
|
@@ -669,7 +669,7 @@ class ReturnSender {
|
|
|
669
669
|
const payload = {};
|
|
670
670
|
const meta = {
|
|
671
671
|
actions: this._visitedInteractions.slice(),
|
|
672
|
-
prompts: this.
|
|
672
|
+
prompts: this.prompts
|
|
673
673
|
};
|
|
674
674
|
|
|
675
675
|
if (req) {
|
|
@@ -697,7 +697,7 @@ class ReturnSender {
|
|
|
697
697
|
_createMeta (req = null, res = null) { // eslint-disable-line no-unused-vars
|
|
698
698
|
const meta = {
|
|
699
699
|
visitedInteractions: this._visitedInteractions.slice(),
|
|
700
|
-
prompts: this.
|
|
700
|
+
prompts: this.prompts
|
|
701
701
|
};
|
|
702
702
|
|
|
703
703
|
if (req) {
|
package/src/Tester.js
CHANGED
|
@@ -19,8 +19,12 @@ const Router = require('./Router'); // eslint-disable-line no-unused-vars
|
|
|
19
19
|
const ReducerWrapper = require('./ReducerWrapper'); // eslint-disable-line no-unused-vars
|
|
20
20
|
const { FEATURE_TEXT } = require('./features');
|
|
21
21
|
const LLMMockProvider = require('./LLMMockProvider');
|
|
22
|
+
const PromptAssert = require('./testTools/PromptAssert');
|
|
22
23
|
|
|
23
24
|
/** @typedef {import('./Processor').ProcessorOptions<Router>} ProcessorOptions */
|
|
25
|
+
/** @typedef {import('./LLM').PromptInfo} PromptInfo */
|
|
26
|
+
/** @typedef {import('./LLM').LLMRole} LLMRole */
|
|
27
|
+
/** @typedef {import('./LLM').LLMMessage} LLMMessage */
|
|
24
28
|
|
|
25
29
|
/**
|
|
26
30
|
* Utility for testing requests
|
|
@@ -128,6 +132,9 @@ class Tester {
|
|
|
128
132
|
this.responses = [];
|
|
129
133
|
this.actions = [];
|
|
130
134
|
|
|
135
|
+
/** @type {PromptInfo[]} */
|
|
136
|
+
this.prompts = [];
|
|
137
|
+
|
|
131
138
|
/**
|
|
132
139
|
* @prop {object} predefined test data to use
|
|
133
140
|
*/
|
|
@@ -201,6 +208,7 @@ class Tester {
|
|
|
201
208
|
this._actionsCollector = [];
|
|
202
209
|
this._pluginBlocksCollector = [];
|
|
203
210
|
this._responsesMock = [];
|
|
211
|
+
this.prompts = [];
|
|
204
212
|
}
|
|
205
213
|
|
|
206
214
|
/**
|
|
@@ -259,6 +267,7 @@ class Tester {
|
|
|
259
267
|
throw Object.assign(new Error(`Processor failed with status ${res.status}`), { code: res.status });
|
|
260
268
|
}
|
|
261
269
|
this.responses = messageSender.responses;
|
|
270
|
+
this.prompts = messageSender.prompts;
|
|
262
271
|
this.pluginBlocks = this._pluginBlocksCollector;
|
|
263
272
|
this.actions = this._actionsCollector;
|
|
264
273
|
this._actionsCollector = [];
|
|
@@ -388,6 +397,36 @@ class Tester {
|
|
|
388
397
|
this.storage.saveState(stateObj);
|
|
389
398
|
}
|
|
390
399
|
|
|
400
|
+
/**
|
|
401
|
+
*
|
|
402
|
+
* @returns {PromptAssert}
|
|
403
|
+
*/
|
|
404
|
+
anyPrompt () {
|
|
405
|
+
return new PromptAssert(this.prompts);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
*
|
|
410
|
+
* @returns {PromptAssert}
|
|
411
|
+
*/
|
|
412
|
+
lastPrompt () {
|
|
413
|
+
return new PromptAssert(
|
|
414
|
+
this.prompts.length
|
|
415
|
+
? [this.prompts[this.prompts.length - 1]]
|
|
416
|
+
: []
|
|
417
|
+
);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
*
|
|
422
|
+
* @returns {LLMMessage}
|
|
423
|
+
*/
|
|
424
|
+
getLastPromptResult () {
|
|
425
|
+
return this.prompts.length
|
|
426
|
+
? this.prompts[this.prompts.length - 1].result
|
|
427
|
+
: null;
|
|
428
|
+
}
|
|
429
|
+
|
|
391
430
|
/**
|
|
392
431
|
* Assert, that state contains a subset of provided value
|
|
393
432
|
*
|
|
@@ -606,9 +645,10 @@ class Tester {
|
|
|
606
645
|
/**
|
|
607
646
|
* Prints last conversation turnaround
|
|
608
647
|
*
|
|
609
|
-
* @param {boolean} [
|
|
648
|
+
* @param {boolean} [full=false]
|
|
649
|
+
* @param {boolean} [showPrivateKeys=false]
|
|
610
650
|
*/
|
|
611
|
-
debug (showPrivateKeys = false) {
|
|
651
|
+
debug (full = false, showPrivateKeys = false) {
|
|
612
652
|
// eslint-disable-next-line no-console
|
|
613
653
|
console.log(
|
|
614
654
|
'\n===== actions =====\n',
|
|
@@ -625,6 +665,8 @@ class Tester {
|
|
|
625
665
|
Object.entries(this.getState().state)
|
|
626
666
|
.filter((e) => showPrivateKeys || !e[0].startsWith('_'))
|
|
627
667
|
),
|
|
668
|
+
'\n------- LLM -------\n',
|
|
669
|
+
...PromptAssert.debug(this.prompts, full, true),
|
|
628
670
|
'\n===================\n'
|
|
629
671
|
);
|
|
630
672
|
}
|
package/src/graphApi/GraphApi.js
CHANGED
|
@@ -8,6 +8,7 @@ const WingbotApiConnector = require('./WingbotApiConnector');
|
|
|
8
8
|
// @ts-ignore
|
|
9
9
|
const packageJson = require('../../package.json');
|
|
10
10
|
const headersToAuditMeta = require('../utils/headersToAuditMeta');
|
|
11
|
+
const gqlRules = require('./gqlRules');
|
|
11
12
|
|
|
12
13
|
const DEFAULT_GROUPS = ['botEditor', 'botAdmin', 'appToken'];
|
|
13
14
|
const KEYS_URL = 'https://api.wingbot.ai/keys';
|
|
@@ -27,6 +28,12 @@ const DEFAULT_CACHE = 86400000; // 24 hours
|
|
|
27
28
|
/** @typedef {import('../CallbackAuditLog')} AuditLog */
|
|
28
29
|
/** @typedef {import('graphql')} GqlLib */
|
|
29
30
|
|
|
31
|
+
/**
|
|
32
|
+
* @typedef {object} Logger
|
|
33
|
+
* @prop {Function} log
|
|
34
|
+
* @prop {Function} error
|
|
35
|
+
*/
|
|
36
|
+
|
|
30
37
|
/**
|
|
31
38
|
* Experimental chatbot API
|
|
32
39
|
*/
|
|
@@ -41,8 +48,11 @@ class GraphApi {
|
|
|
41
48
|
* @param {string[]} [options.groups] - list of allowed bot groups
|
|
42
49
|
* @param {boolean} [options.useBundledGql] - uses library bundled graphql definition
|
|
43
50
|
* @param {AuditLog} [options.auditLog]
|
|
51
|
+
* @param {boolean} [options.isProduction]
|
|
52
|
+
* @param {boolean} [options.hideVerboseErrors]
|
|
53
|
+
* @param {Logger} [log=console]
|
|
44
54
|
*/
|
|
45
|
-
constructor (apis, options) {
|
|
55
|
+
constructor (apis, options, log = console) {
|
|
46
56
|
this._root = {
|
|
47
57
|
version () {
|
|
48
58
|
return packageJson.version;
|
|
@@ -65,6 +75,13 @@ class GraphApi {
|
|
|
65
75
|
}
|
|
66
76
|
};
|
|
67
77
|
|
|
78
|
+
this._log = log;
|
|
79
|
+
this._options = {
|
|
80
|
+
hideVerboseErrors: true,
|
|
81
|
+
isProduction: true,
|
|
82
|
+
...options
|
|
83
|
+
};
|
|
84
|
+
|
|
68
85
|
Object.assign(opts, options);
|
|
69
86
|
|
|
70
87
|
apis.forEach((api) => Object.assign(this._root, api));
|
|
@@ -152,6 +169,19 @@ class GraphApi {
|
|
|
152
169
|
|
|
153
170
|
const schema = await this._schema();
|
|
154
171
|
|
|
172
|
+
const { isProduction = true, hideVerboseErrors } = this._options;
|
|
173
|
+
const ast = this._gql.parse(body.query);
|
|
174
|
+
const errors = this._gql.validate(
|
|
175
|
+
schema,
|
|
176
|
+
ast,
|
|
177
|
+
gqlRules(body.variables, isProduction, hideVerboseErrors, this._log)
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
if (errors.length > 0) {
|
|
181
|
+
this._log.error('GQL failed', errors);
|
|
182
|
+
return { errors };
|
|
183
|
+
}
|
|
184
|
+
|
|
155
185
|
const ctx = {
|
|
156
186
|
token,
|
|
157
187
|
groups: this._defaultGroups,
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/* eslint-disable global-require */
|
|
2
|
+
/**
|
|
3
|
+
* @author David Menger
|
|
4
|
+
*/
|
|
5
|
+
'use strict';
|
|
6
|
+
|
|
7
|
+
/** @typedef {import('graphql').ValidationRule} ValidationRule */
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @typedef {object} Logger
|
|
11
|
+
* @prop {Function} log
|
|
12
|
+
* @prop {Function} error
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
*
|
|
17
|
+
* @param {object} variables
|
|
18
|
+
* @param {boolean} isProduction
|
|
19
|
+
* @param {boolean} hideVerboseErrors
|
|
20
|
+
* @param {Logger} [log=console]
|
|
21
|
+
* @returns {ValidationRule[]}
|
|
22
|
+
*/
|
|
23
|
+
function gqlRules (variables, isProduction, hideVerboseErrors, log = console) {
|
|
24
|
+
// OPTIMIZATION FOR LAMBDA PERFORMANCE
|
|
25
|
+
const { GraphQLError, NoSchemaIntrospectionCustomRule } = require('graphql');
|
|
26
|
+
const { createComplexityRule, simpleEstimator } = require('graphql-query-complexity');
|
|
27
|
+
const depthLimit = require('graphql-depth-limit');
|
|
28
|
+
|
|
29
|
+
return [
|
|
30
|
+
...(hideVerboseErrors ? [NoSchemaIntrospectionCustomRule] : []),
|
|
31
|
+
depthLimit(10),
|
|
32
|
+
createComplexityRule({
|
|
33
|
+
// The maximum allowed query complexity, queries above this threshold will be rejected
|
|
34
|
+
maximumComplexity: 1000,
|
|
35
|
+
|
|
36
|
+
// The query variables. This is needed because the variables are not available
|
|
37
|
+
// in the visitor of the graphql-js library
|
|
38
|
+
variables,
|
|
39
|
+
|
|
40
|
+
// The context object for the request (optional)
|
|
41
|
+
context: {},
|
|
42
|
+
|
|
43
|
+
// The maximum number of query nodes to evaluate (fields, fragments, composite types).
|
|
44
|
+
// If a query contains more than the specified number of nodes, the complexity rule will
|
|
45
|
+
// throw an error, regardless of the complexity of the query.
|
|
46
|
+
//
|
|
47
|
+
// Default: 10_000
|
|
48
|
+
maxQueryNodes: 10000,
|
|
49
|
+
|
|
50
|
+
// Optional callback function to retrieve the determined query complexity
|
|
51
|
+
// Will be invoked whether the query is rejected or not
|
|
52
|
+
// This can be used for logging or to implement rate limiting
|
|
53
|
+
onComplete: (complexity) => {
|
|
54
|
+
if (!isProduction) {
|
|
55
|
+
log.log('Determined query complexity: ', complexity);
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
|
|
59
|
+
// Optional function to create a custom error
|
|
60
|
+
createError: (max, actual) => {
|
|
61
|
+
const msg = `GQL Query too complex: ${actual}. Maximum allowed: ${max}`;
|
|
62
|
+
|
|
63
|
+
if (hideVerboseErrors) {
|
|
64
|
+
log.error(msg);
|
|
65
|
+
return new GraphQLError('');
|
|
66
|
+
}
|
|
67
|
+
log.log(msg);
|
|
68
|
+
return new GraphQLError(`Query is too complex: ${actual}. Maximum allowed complexity: ${max}`);
|
|
69
|
+
},
|
|
70
|
+
|
|
71
|
+
// Add any number of estimators. The estimators are invoked in order, the first
|
|
72
|
+
// numeric value that is being returned by an estimator is used as the field complexity.
|
|
73
|
+
// If no estimator returns a value, an exception is raised.
|
|
74
|
+
estimators: [
|
|
75
|
+
// Add more estimators here...
|
|
76
|
+
|
|
77
|
+
// This will assign each field a complexity of 1 if no other estimator
|
|
78
|
+
// returned a value.
|
|
79
|
+
simpleEstimator({
|
|
80
|
+
defaultComplexity: 1
|
|
81
|
+
})
|
|
82
|
+
]
|
|
83
|
+
})
|
|
84
|
+
];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
module.exports = gqlRules;
|
package/src/graphApi/schema.gql
CHANGED
|
@@ -109,9 +109,24 @@ type LLMMessage {
|
|
|
109
109
|
toolCalls: [ToolCall!]
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
+
type VectorSearchDocument {
|
|
113
|
+
id: String!
|
|
114
|
+
name: String!
|
|
115
|
+
text: String!
|
|
116
|
+
cosineDistance: Float!
|
|
117
|
+
excludedByCosineDistanceThreshold: Boolean!
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
type VectorSearchResult {
|
|
121
|
+
maximalCosineDistanceThreshold: Float!
|
|
122
|
+
nearestNeighbourCount: Int!
|
|
123
|
+
resultDocuments: [VectorSearchDocument!]!
|
|
124
|
+
}
|
|
125
|
+
|
|
112
126
|
type PromptInfo {
|
|
113
127
|
prompt: [LLMMessage!]!
|
|
114
128
|
result: LLMMessage!
|
|
129
|
+
vectorSearchResult: VectorSearchResult
|
|
115
130
|
}
|
|
116
131
|
|
|
117
132
|
type UserInteraction {
|