wingbot 3.74.0 → 3.74.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/src/LLM.js CHANGED
@@ -8,6 +8,16 @@ const { PHONE_REGEX, EMAIL_REGEX } = require('./systemEntities/regexps');
8
8
  const getCondition = require('./utils/getCondition');
9
9
  const stateData = require('./utils/stateData');
10
10
  const Ai = require('./Ai');
11
+ const {
12
+ PRESET_DEFAULT,
13
+ PRESET_ROUTING,
14
+ PRESET_EMBEDDINGS,
15
+ ROLE_USER,
16
+ ROLE_ASSISTANT,
17
+ ROLE_SYSTEM,
18
+ FILTER_SCOPE_CONVERSATION
19
+ } = require('./LLMConsts');
20
+ const LLMSession = require('./LLMSession');
11
21
  // const getCondition = require('./utils/getCondition');
12
22
 
13
23
  /** @typedef {import('./Responder')} Responder */
@@ -19,7 +29,10 @@ const Ai = require('./Ai');
19
29
  /** @typedef {import('./LLMSession').ToolCall} ToolCall */
20
30
  /** @typedef {import('./LLMSession').LLMRole} LLMRole */
21
31
  /** @typedef {import('./LLMSession').FilterScope} FilterScope */
22
- /** @typedef {import('./LLMSession')} LLMSession */
32
+ /** @typedef {import('./LLMSession').JsonSchemaProp} JsonSchemaProp */
33
+ /** @typedef {import('./LLMSession').JsonSchemaProp} SimpleJsonSchema */
34
+ /** @typedef {import('./LLMSession').ToolFunction} ToolFunction */
35
+
23
36
  /** @typedef {import('./transcript/transcriptFromHistory').Transcript} Transcript */
24
37
  /** @typedef {import('./utils/getCondition').ConditionDefinition} ConditionDefinition */
25
38
  /** @typedef {import('./utils/getCondition').ConditionContext} ConditionContext */
@@ -74,12 +87,24 @@ const Ai = require('./Ai');
74
87
  * @callback LLMChatProviderPrompt
75
88
  * @param {LLMMessage[]} prompt
76
89
  * @param {LLMProviderOptions} [options]
90
+ * @param {ToolFunction[]} [tools]
77
91
  * @returns {Promise<LLMMessage>}
78
92
  */
79
93
 
94
+ /**
95
+ * @typedef {object} ForcedFn
96
+ * @prop {'function'|string} [type]
97
+ * @prop {string} name
98
+ */
99
+
80
100
  /**
81
101
  * @typedef {object} LLMProviderOptions
82
102
  * @prop {string} [model]
103
+ * @prop {boolean} [parallelToolCalls]
104
+ * @prop {'auto'|'required'|'none'|ForcedFn|string} [toolChoice]
105
+ * @prop {'none'|'low'|'medium'|'high'|string} [reasoningEffort]
106
+ * @prop {'low'|'medium'|'high'|string} [verbosity]
107
+ * @prop {'text'|SimpleJsonSchema} [responseFormat]
83
108
  */
84
109
 
85
110
  /**
@@ -95,15 +120,29 @@ const Ai = require('./Ai');
95
120
  /** @typedef {import('node-fetch').default} Fetch */
96
121
 
97
122
  /**
98
- * @typedef {object} LLMConfiguration
99
- * @prop {LLMChatProvider} provider
100
- * @prop {string} [model]
123
+ * @typedef {object} LLMOptionsExt
101
124
  * @prop {number} [transcriptLength=-5]
102
125
  * @prop {'gpt'|string} [transcriptFlag]
103
126
  * @prop {boolean} [transcriptAnonymize]
127
+ */
128
+
129
+ /** @typedef {LLMOptionsExt & LLMProviderOptions} LLMOptions */
130
+ /** @typedef {LLMOptions & { preset?: LLMPresetName}} LLMCallOptions */
131
+ /** @typedef {'default'|'routing'|'embeddings'|string} LLMPresetName */
132
+
133
+ /** @typedef {LLMCallOptions|LLMPresetName} LLMCallPreset */
134
+
135
+ /**
136
+ * @typedef {object} LLMGlobalConfigExt
137
+ * @prop {LLMChatProvider} provider
104
138
  * @prop {Persona|string|null} [persona]
105
139
  * @prop {LLMLogger} [logger]
106
140
  * @prop {boolean} [disableLLM]
141
+ * @prop {{ [key: LLMPresetName]: LLMOptions }} [presets]
142
+ */
143
+
144
+ /**
145
+ * @typedef {LLMGlobalConfigExt & LLMOptions} LLMGlobalConfig
107
146
  */
108
147
 
109
148
  /**
@@ -129,6 +168,12 @@ const Ai = require('./Ai');
129
168
  * @prop {LogPrompt} logPrompt
130
169
  */
131
170
 
171
+ /**
172
+ * @typedef {object} Logger
173
+ * @prop {Function} log
174
+ * @prop {Function} error
175
+ */
176
+
132
177
  /**
133
178
  * @typedef {object} VectorSearchDocument
134
179
  * @property {string} id
@@ -148,19 +193,28 @@ const Ai = require('./Ai');
148
193
  */
149
194
  class LLM {
150
195
 
196
+ /** @type {LLMPresetName} */
197
+ static PRESET_DEFAULT = PRESET_DEFAULT;
198
+
199
+ /** @type {LLMPresetName} */
200
+ static PRESET_ROUTING = PRESET_ROUTING;
201
+
202
+ /** @type {LLMPresetName} */
203
+ static PRESET_EMBEDDINGS = PRESET_EMBEDDINGS;
204
+
151
205
  /** @type {LLMRole} */
152
- static ROLE_USER = 'user';
206
+ static ROLE_USER = ROLE_USER;
153
207
 
154
208
  /** @type {LLMRole} */
155
- static ROLE_ASSISTANT = 'assistant';
209
+ static ROLE_ASSISTANT = ROLE_ASSISTANT;
156
210
 
157
211
  /** @type {LLMRole} */
158
- static ROLE_SYSTEM = 'system';
212
+ static ROLE_SYSTEM = ROLE_SYSTEM;
159
213
 
160
214
  static GPT_FLAG = 'gpt';
161
215
 
162
216
  /** @type {FilterScope} */
163
- static FILTER_SCOPE_CONVERSATION = 'conversation';
217
+ static FILTER_SCOPE_CONVERSATION = FILTER_SCOPE_CONVERSATION;
164
218
 
165
219
  static EVALUATION_ACTIONS = {
166
220
  DISCARD: '_DISCARD'
@@ -174,11 +228,20 @@ class LLM {
174
228
 
175
229
  /**
176
230
  *
177
- * @param {LLMConfiguration} configuration
231
+ * @param {LLMGlobalConfig} configuration
178
232
  * @param {Ai} ai
233
+ * @param {Logger} [log=console]
179
234
  */
180
- constructor (configuration, ai) {
181
- const { provider, ...rest } = configuration;
235
+ constructor (configuration, ai, log = console) {
236
+ const {
237
+ provider,
238
+ presets = {},
239
+ transcriptFlag = null,
240
+ transcriptLength = 5,
241
+ transcriptAnonymize = false,
242
+ model = null,
243
+ ...rest
244
+ } = configuration;
182
245
 
183
246
  this._configuration = {
184
247
  transcriptFlag: null,
@@ -190,6 +253,30 @@ class LLM {
190
253
  ...rest
191
254
  };
192
255
 
256
+ /** @type {LLMOptions} */
257
+ const defaultPreset = {
258
+ model,
259
+ transcriptFlag,
260
+ transcriptLength,
261
+ transcriptAnonymize
262
+ };
263
+
264
+ this._presets = new Map(
265
+ Object.entries(presets)
266
+ .map(([k, v]) => [k, {
267
+ ...defaultPreset,
268
+ ...v
269
+ }])
270
+ );
271
+
272
+ if (!this._presets.has(LLM.PRESET_DEFAULT)) {
273
+ this._presets.set(LLM.PRESET_DEFAULT, defaultPreset);
274
+ }
275
+
276
+ if (!this._presets.has(LLM.PRESET_ROUTING)) {
277
+ this._presets.set(LLM.PRESET_ROUTING, defaultPreset);
278
+ }
279
+
193
280
  /** @type {LLMChatProvider} */
194
281
  this._provider = provider;
195
282
 
@@ -197,6 +284,8 @@ class LLM {
197
284
 
198
285
  /** @type {LLMMessage} */
199
286
  this._lastResult = null;
287
+
288
+ this.log = log;
200
289
  }
201
290
 
202
291
  /**
@@ -214,20 +303,51 @@ class LLM {
214
303
  }
215
304
 
216
305
  /**
217
- * @returns {Omit<LLMConfiguration, 'provider'>}
306
+ * @deprecated
307
+ * @returns {Omit<LLMGlobalConfig, 'provider'>}
218
308
  */
219
309
  get configuration () {
220
310
  return this._configuration;
221
311
  }
222
312
 
313
+ /**
314
+ * @returns {LLMSession}
315
+ */
316
+ session () {
317
+ return new LLMSession(this);
318
+ }
319
+
223
320
  /**
224
321
  *
225
- * @param {Partial<LLMConfiguration>} override
322
+ * @param {LLMCallPreset} [requestedPreset]
226
323
  */
227
- setSessionConfig (override) {
228
- Object.assign(this._configuration, override);
324
+ llmOptions (requestedPreset = LLM.PRESET_DEFAULT) {
325
+ let preset;
326
+ let override;
327
+
328
+ if (typeof requestedPreset === 'string') {
329
+ preset = requestedPreset;
330
+ } else {
331
+ ({ preset = LLM.PRESET_DEFAULT, ...override } = requestedPreset);
332
+ }
333
+
334
+ if (!this._presets.has(preset)) {
335
+ throw new Error(`LLM Preset '${preset}' does not exist.`);
336
+ }
337
+ return {
338
+ ...this._presets.get(preset),
339
+ ...override
340
+ };
229
341
  }
230
342
 
343
+ // /**
344
+ // *
345
+ // * @param {Partial<LLMConfiguration>} override
346
+ // */
347
+ // setSessionConfig (override) {
348
+ // Object.assign(this._configuration, override);
349
+ // }
350
+
231
351
  /**
232
352
  *
233
353
  * @param {Transcript[]} chat
@@ -250,19 +370,14 @@ class LLM {
250
370
  /**
251
371
  *
252
372
  * @param {LLMSession} session
253
- * @param {LLMProviderOptions} [options={}]
373
+ * @param {LLMCallPreset} [preset={}]
254
374
  * @param {LLMLogOptions} [logOptions]
255
375
  * @returns {Promise<LLMMessage>}
256
376
  */
257
- async generate (session, options = {}, logOptions = {}) {
258
- /** @type {LLMProviderOptions} */
259
- const opts = {
260
- ...(this._configuration.model && { model: this._configuration.model }),
261
- ...options
262
- };
263
-
264
- const prompt = session.toArray(true);
265
- const result = await this._provider.requestChat(prompt, opts);
377
+ async generate (session, preset = {}, logOptions = {}) {
378
+ const opts = this.llmOptions(preset);
379
+ const prompt = await session.toArray(true);
380
+ const result = await this._provider.requestChat(prompt, opts, session.tools);
266
381
  this.logPrompt(prompt, result, logOptions.vectorSearchResult);
267
382
  return result;
268
383
  }
@@ -280,29 +395,6 @@ class LLM {
280
395
  });
281
396
  }
282
397
 
283
- /**
284
- *
285
- * @param {LLMMessage} result
286
- * @returns {LLMMessage[]}
287
- */
288
- static toMessages (result) {
289
- let filtered = result.content
290
- .replace(/\n\n\n+/g, '\n\n')
291
- .split(/\n\n+(?!\s*-)/g)
292
- .map((t) => t.replace(/\s*\n\s+/g, '\n')
293
- .trim())
294
- .filter((t) => !!t);
295
-
296
- if (result.finishReason === 'length' && filtered.length <= 0) {
297
- filtered = filtered.slice(0, filtered.length - 1);
298
- }
299
-
300
- return filtered.map((content) => ({
301
- content,
302
- role: result.role
303
- }));
304
- }
305
-
306
398
  /**
307
399
  *
308
400
  * @param {EvaluationRule[]} rules
@@ -0,0 +1,42 @@
1
+ /**
2
+ * @author David Menger
3
+ */
4
+ 'use strict';
5
+
6
+ /** @typedef {import('./LLMSession').FilterScope} FilterScope */
7
+ /** @typedef {import('./LLM').LLMPresetName} LLMPresetName */
8
+ /** @typedef {import('./LLMSession').LLMRole} LLMRole */
9
+
10
+ /** @type {LLMPresetName} */
11
+ const PRESET_DEFAULT = 'default';
12
+
13
+ /** @type {LLMPresetName} */
14
+ const PRESET_ROUTING = 'routing';
15
+
16
+ /** @type {LLMPresetName} */
17
+ const PRESET_EMBEDDINGS = 'embeddings';
18
+
19
+ /** @type {LLMRole} */
20
+ const ROLE_USER = 'user';
21
+
22
+ /** @type {LLMRole} */
23
+ const ROLE_ASSISTANT = 'assistant';
24
+
25
+ /** @type {LLMRole} */
26
+ const ROLE_SYSTEM = 'system';
27
+
28
+ const GPT_FLAG = 'gpt';
29
+
30
+ /** @type {FilterScope} */
31
+ const FILTER_SCOPE_CONVERSATION = 'conversation';
32
+
33
+ module.exports = {
34
+ PRESET_DEFAULT,
35
+ PRESET_ROUTING,
36
+ PRESET_EMBEDDINGS,
37
+ ROLE_USER,
38
+ ROLE_ASSISTANT,
39
+ ROLE_SYSTEM,
40
+ GPT_FLAG,
41
+ FILTER_SCOPE_CONVERSATION
42
+ };
@@ -35,9 +35,36 @@ class LLMMockProvider {
35
35
  */
36
36
  // eslint-disable-next-line no-unused-vars
37
37
  async requestChat (prompt, options) {
38
+
38
39
  if (prompt.length === 0) {
39
40
  throw new Error('Empty prompt');
40
41
  }
42
+
43
+ if (prompt.some((p) => p?.content?.includes('THROW EXCEPTION'))) {
44
+ throw new Error('THROW EXCEPTION');
45
+ }
46
+
47
+ if (prompt.some((p) => p.toolCallId)) {
48
+ return {
49
+ role: LLM.ROLE_ASSISTANT,
50
+ content: 'got tool call id'
51
+ };
52
+ }
53
+
54
+ if (prompt.some((p) => p?.content?.includes('CALL MOCK TOOL'))) {
55
+ return {
56
+ role: LLM.ROLE_ASSISTANT,
57
+ toolCalls: [
58
+ {
59
+ id: `${Date.now()}${Math.floor(Math.random() * 10)}`,
60
+ type: 'function',
61
+ name: 'mock',
62
+ args: JSON.stringify({ mock: true })
63
+ }
64
+ ]
65
+ };
66
+ }
67
+
41
68
  // const stats = prompt.reduce((o, m) => Object.assign(o, {
42
69
  // [m.role]: (o[m.role] || 0) + 1
43
70
  // }), { system: 0, assistant: 0, user: 0 });
@@ -55,13 +82,47 @@ class LLMMockProvider {
55
82
  // content: `${statsText} > ${LLMMockProvider.DEFAULT_MODEL}: ${prompt.map((m) => m.content).join(' ')}`
56
83
  // };
57
84
 
85
+ const text = `${options.model || LLMMockProvider.DEFAULT_MODEL}:${prompt.filter((m) => m.content).map((m) => `[${m.role}] ${m.content}`).join(' | ')}`;
86
+
58
87
  return {
59
88
  role: LLM.ROLE_ASSISTANT,
60
89
  finishReason: 'length',
61
- content: `${options.model || LLMMockProvider.DEFAULT_MODEL}:${prompt.map((m) => m.content).join(' ')}`
90
+ content: options.responseFormat && options.responseFormat !== 'text'
91
+ ? JSON.stringify(this._mockFromSchema(options.responseFormat))
92
+ : text
62
93
  };
63
94
  }
64
95
 
96
+ _mockFromSchema (schema) {
97
+ if (schema.properties) {
98
+ const obj = {};
99
+ for (const [key, prop] of Object.entries(schema.properties)) {
100
+ obj[key] = this._mockFromProp(prop);
101
+ }
102
+ return obj;
103
+ }
104
+ return {};
105
+ }
106
+
107
+ _mockFromProp (prop) {
108
+ if (prop.enum) {
109
+ return prop.enum[0];
110
+ }
111
+ switch (prop.type) {
112
+ case 'number':
113
+ return prop.minimum || 0;
114
+ case 'boolean':
115
+ return false;
116
+ case 'array':
117
+ return [];
118
+ case 'object':
119
+ return this._mockFromSchema(prop);
120
+ case 'string':
121
+ default:
122
+ return 'mock';
123
+ }
124
+ }
125
+
65
126
  }
66
127
 
67
128
  module.exports = LLMMockProvider;