backend-manager 3.2.115 → 3.2.117
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "backend-manager",
|
|
3
|
-
"version": "3.2.
|
|
3
|
+
"version": "3.2.117",
|
|
4
4
|
"description": "Quick tools for developing Firebase functions",
|
|
5
5
|
"main": "src/manager/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -45,8 +45,8 @@
|
|
|
45
45
|
"cors": "^2.8.5",
|
|
46
46
|
"dotenv": "^16.4.5",
|
|
47
47
|
"express": "^4.19.2",
|
|
48
|
-
"firebase-admin": "^
|
|
49
|
-
"firebase-functions": "^
|
|
48
|
+
"firebase-admin": "^12.1.0",
|
|
49
|
+
"firebase-functions": "^5.0.1",
|
|
50
50
|
"fs-jetpack": "^5.1.0",
|
|
51
51
|
"glob": "^10.3.12",
|
|
52
52
|
"hcaptcha": "^0.1.1",
|
|
@@ -50,6 +50,11 @@ Middleware.prototype.run = function (libPath, options) {
|
|
|
50
50
|
const routesDir = path.resolve(options.routesDir, libPath.replace('.js', ''));
|
|
51
51
|
const schemasDir = path.resolve(options.schemasDir);
|
|
52
52
|
|
|
53
|
+
// Wakeup trigger (quit immediately if wakeup is true to avoid cold start on a future request)
|
|
54
|
+
if (data.wakeup) {
|
|
55
|
+
return assistant.respond({wakeup: true});
|
|
56
|
+
}
|
|
57
|
+
|
|
53
58
|
// Load library
|
|
54
59
|
let library;
|
|
55
60
|
try {
|
|
@@ -18,7 +18,9 @@ Utilities.prototype.iterateCollection = function (callback, options) {
|
|
|
18
18
|
|
|
19
19
|
return new Promise(function(resolve, reject) {
|
|
20
20
|
let batch = -1;
|
|
21
|
+
let collectionCount = 0;
|
|
21
22
|
|
|
23
|
+
// Set defaults
|
|
22
24
|
options = options || {};
|
|
23
25
|
options.collection = options.collection || '';
|
|
24
26
|
options.batchSize = options.batchSize || 1000;
|
|
@@ -31,21 +33,26 @@ Utilities.prototype.iterateCollection = function (callback, options) {
|
|
|
31
33
|
: options.prefetchCursor;
|
|
32
34
|
options.log = options.log;
|
|
33
35
|
|
|
36
|
+
// List all documents in a collection
|
|
34
37
|
async function listAllDocuments(nextPageToken) {
|
|
35
|
-
let query = admin.firestore().collection(options.collection)
|
|
38
|
+
let query = admin.firestore().collection(options.collection);
|
|
36
39
|
|
|
40
|
+
// Insert where clauses
|
|
37
41
|
options.where
|
|
38
42
|
.forEach(clause => {
|
|
39
43
|
query = query.where(clause.field, clause.operator, clause.value);
|
|
40
44
|
});
|
|
41
45
|
|
|
46
|
+
// Insert orderBy
|
|
42
47
|
if (options.orderBy) {
|
|
43
48
|
query = query.orderBy(options.orderBy);
|
|
44
49
|
}
|
|
45
50
|
|
|
51
|
+
// Process the first batch differently
|
|
46
52
|
if (batch === -1) {
|
|
47
53
|
let prefetchedCursor = null;
|
|
48
54
|
|
|
55
|
+
// Prefetch the cursor
|
|
49
56
|
if (options.prefetchCursor) {
|
|
50
57
|
prefetchedCursor = await admin.firestore().doc(`${options.collection}/${options.startAt || options.startAfter}`)
|
|
51
58
|
.get()
|
|
@@ -56,11 +63,30 @@ Utilities.prototype.iterateCollection = function (callback, options) {
|
|
|
56
63
|
}
|
|
57
64
|
}
|
|
58
65
|
|
|
66
|
+
// Insert startAt or startAfter
|
|
59
67
|
if (options.startAt) {
|
|
60
68
|
query = query.startAt(prefetchedCursor || options.startAt);
|
|
61
69
|
} else if (options.startAfter) {
|
|
62
70
|
query = query.startAfter(prefetchedCursor || options.startAfter);
|
|
63
71
|
}
|
|
72
|
+
|
|
73
|
+
// Calculate count
|
|
74
|
+
const collectionCountResult = await query.count().get()
|
|
75
|
+
.then((r) => r.data().count)
|
|
76
|
+
.catch((e) => e);
|
|
77
|
+
|
|
78
|
+
// Check for errors
|
|
79
|
+
if (collectionCountResult instanceof Error) {
|
|
80
|
+
return reject(collectionCountResult);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Set collection count
|
|
84
|
+
collectionCount = collectionCountResult;
|
|
85
|
+
|
|
86
|
+
// Log
|
|
87
|
+
if (options.log) {
|
|
88
|
+
console.log('Total count:', collectionCount);
|
|
89
|
+
}
|
|
64
90
|
}
|
|
65
91
|
|
|
66
92
|
// Start at next page
|
|
@@ -71,6 +97,7 @@ Utilities.prototype.iterateCollection = function (callback, options) {
|
|
|
71
97
|
// batchSize
|
|
72
98
|
query = query.limit(options.batchSize);
|
|
73
99
|
|
|
100
|
+
// Get
|
|
74
101
|
query.get()
|
|
75
102
|
.then(async (snap) => {
|
|
76
103
|
const lastVisible = snap.docs[snap.docs.length - 1];
|
|
@@ -85,22 +112,26 @@ Utilities.prototype.iterateCollection = function (callback, options) {
|
|
|
85
112
|
console.log('Processing batch:', batch);
|
|
86
113
|
}
|
|
87
114
|
|
|
88
|
-
callback(
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
115
|
+
callback(
|
|
116
|
+
{
|
|
117
|
+
snap: snap,
|
|
118
|
+
docs: snap.docs.map(x => x),
|
|
119
|
+
},
|
|
120
|
+
batch,
|
|
121
|
+
collectionCount,
|
|
122
|
+
)
|
|
123
|
+
.then(r => {
|
|
124
|
+
// Construct a new query starting at this document
|
|
125
|
+
if (lastVisible) {
|
|
126
|
+
listAllDocuments(lastVisible)
|
|
127
|
+
} else {
|
|
128
|
+
return resolve();
|
|
129
|
+
}
|
|
130
|
+
})
|
|
131
|
+
.catch((e) => {
|
|
132
|
+
console.error('Callback failed', e);
|
|
133
|
+
return reject(e);
|
|
134
|
+
});
|
|
104
135
|
|
|
105
136
|
})
|
|
106
137
|
.catch((e) => {
|
|
@@ -109,6 +140,7 @@ Utilities.prototype.iterateCollection = function (callback, options) {
|
|
|
109
140
|
});
|
|
110
141
|
}
|
|
111
142
|
|
|
143
|
+
// Run
|
|
112
144
|
listAllDocuments();
|
|
113
145
|
});
|
|
114
146
|
};
|
package/src/manager/index.js
CHANGED
|
@@ -68,7 +68,9 @@ Manager.prototype.init = function (exporter, options) {
|
|
|
68
68
|
// Load libraries
|
|
69
69
|
self.libraries = {
|
|
70
70
|
// Third-party
|
|
71
|
-
functions: options.projectType === 'firebase'
|
|
71
|
+
functions: options.projectType === 'firebase'
|
|
72
|
+
? require('firebase-functions')
|
|
73
|
+
: null,
|
|
72
74
|
admin: require('firebase-admin'),
|
|
73
75
|
cors: require('cors')({ origin: true }),
|
|
74
76
|
sentry: null,
|
|
@@ -1,326 +0,0 @@
|
|
|
1
|
-
const fetch = require('wonderful-fetch');
|
|
2
|
-
const jetpack = require('fs-jetpack');
|
|
3
|
-
const powertools = require('node-powertools');
|
|
4
|
-
const _ = require('lodash');
|
|
5
|
-
const JSON5 = require('json5');
|
|
6
|
-
|
|
7
|
-
const TOKEN_COST_TABLE = {
|
|
8
|
-
// Mar 6th, 2024
|
|
9
|
-
'gpt-4-turbo-preview': {
|
|
10
|
-
input: 0.0100,
|
|
11
|
-
output: 0.0300,
|
|
12
|
-
},
|
|
13
|
-
'gpt-4-1106-preview': {
|
|
14
|
-
input: 0.0100,
|
|
15
|
-
output: 0.0300,
|
|
16
|
-
},
|
|
17
|
-
'gpt-4': {
|
|
18
|
-
input: 0.0300,
|
|
19
|
-
output: 0.0600,
|
|
20
|
-
},
|
|
21
|
-
'gpt-3.5-turbo': {
|
|
22
|
-
input: 0.0005,
|
|
23
|
-
output: 0.0015,
|
|
24
|
-
},
|
|
25
|
-
|
|
26
|
-
// Nov 6th, 2023
|
|
27
|
-
// 'gpt-4-turbo-preview': {
|
|
28
|
-
// input: 0.0100,
|
|
29
|
-
// output: 0.0300,
|
|
30
|
-
// },
|
|
31
|
-
// 'gpt-4-1106-preview': {
|
|
32
|
-
// input: 0.0100,
|
|
33
|
-
// output: 0.0300,
|
|
34
|
-
// },
|
|
35
|
-
// 'gpt-4': {
|
|
36
|
-
// input: 0.0300,
|
|
37
|
-
// output: 0.0600,
|
|
38
|
-
// },
|
|
39
|
-
// 'gpt-3.5-turbo': {
|
|
40
|
-
// input: 0.0010,
|
|
41
|
-
// output: 0.0020,
|
|
42
|
-
// },
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function OpenAI(assistant, key) {
|
|
46
|
-
const self = this;
|
|
47
|
-
|
|
48
|
-
self.assistant = assistant;
|
|
49
|
-
self.Manager = assistant.Manager;
|
|
50
|
-
self.user = assistant.user;
|
|
51
|
-
self.key = key;
|
|
52
|
-
|
|
53
|
-
self.tokens = {
|
|
54
|
-
total: {
|
|
55
|
-
count: 0,
|
|
56
|
-
price: 0,
|
|
57
|
-
},
|
|
58
|
-
input: {
|
|
59
|
-
count: 0,
|
|
60
|
-
price: 0,
|
|
61
|
-
},
|
|
62
|
-
output: {
|
|
63
|
-
count: 0,
|
|
64
|
-
price: 0,
|
|
65
|
-
},
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
return self;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
OpenAI.prototype.request = function (options) {
|
|
72
|
-
const self = this;
|
|
73
|
-
const Manager = self.Manager;
|
|
74
|
-
const assistant = self.assistant;
|
|
75
|
-
|
|
76
|
-
return new Promise(async function(resolve, reject) {
|
|
77
|
-
options = _.merge({}, options);
|
|
78
|
-
|
|
79
|
-
options.model = typeof options.model === 'undefined' ? 'gpt-3.5-turbo' : options.model;
|
|
80
|
-
options.timeout = typeof options.timeout === 'undefined' ? 120000 : options.timeout;
|
|
81
|
-
options.moderate = typeof options.moderate === 'undefined' ? true : options.moderate;
|
|
82
|
-
options.log = typeof options.log === 'undefined' ? false : options.log;
|
|
83
|
-
options.user = options.user || assistant.getUser();
|
|
84
|
-
|
|
85
|
-
options.retries = typeof options.retries === 'undefined' ? 0 : options.retries;
|
|
86
|
-
options.retryTriggers = typeof options.retryTriggers === 'undefined' ? ['network', 'parse'] : options.retryTriggers;
|
|
87
|
-
|
|
88
|
-
options.prompt = options.prompt || {};
|
|
89
|
-
options.prompt.path = options.prompt.path || '';
|
|
90
|
-
options.prompt.content = options.prompt.content || '';
|
|
91
|
-
options.prompt.settings = options.prompt.settings || {};
|
|
92
|
-
|
|
93
|
-
options.message = options.message || {};
|
|
94
|
-
options.message.path = options.message.path || '';
|
|
95
|
-
options.message.content = options.message.content || '';
|
|
96
|
-
options.message.settings = options.message.settings || {};
|
|
97
|
-
|
|
98
|
-
options.history = options.history || {};
|
|
99
|
-
options.history.messages = options.history.messages || [];
|
|
100
|
-
options.history.limit = typeof options.history.limit === 'undefined' ? 5 : options.history.limit;
|
|
101
|
-
|
|
102
|
-
options.response = typeof options.response === 'undefined' ? undefined : options.response;
|
|
103
|
-
options.temperature = typeof options.temperature === 'undefined' ? 0.7 : options.temperature;
|
|
104
|
-
options.maxTokens = typeof options.maxTokens === 'undefined' ? 512 : options.maxTokens;
|
|
105
|
-
|
|
106
|
-
let attempt = 0;
|
|
107
|
-
|
|
108
|
-
function _log() {
|
|
109
|
-
if (!options.log) {
|
|
110
|
-
return;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
assistant.log('callOpenAI():', ...arguments);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
function _load(input) {
|
|
117
|
-
// console.log('*** input!!!', input.content.slice(0, 50), input.path);
|
|
118
|
-
// console.log('*** input.content', input.content.slice(0, 50));
|
|
119
|
-
// console.log('*** input.path', input.path);
|
|
120
|
-
// _log('Loading', input);
|
|
121
|
-
|
|
122
|
-
let content = '';
|
|
123
|
-
|
|
124
|
-
// Load content
|
|
125
|
-
if (input.path) {
|
|
126
|
-
const exists = jetpack.exists(input.path);
|
|
127
|
-
if (!exists) {
|
|
128
|
-
return new Error(`Path ${input.path} not found`);
|
|
129
|
-
} else if (exists === 'dir') {
|
|
130
|
-
return new Error(`Path ${input.path} is a directory`);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
try {
|
|
134
|
-
content = jetpack.read(input.path);
|
|
135
|
-
} catch (e) {
|
|
136
|
-
return new Error(`Error reading file ${input.path}: ${e}`);
|
|
137
|
-
}
|
|
138
|
-
} else {
|
|
139
|
-
content = input.content;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
return powertools.template(content, input.settings).trim();
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
// Log
|
|
146
|
-
_log('Starting', self.key, options);
|
|
147
|
-
|
|
148
|
-
// Load prompt
|
|
149
|
-
const prompt = _load(options.prompt);
|
|
150
|
-
const message = _load(options.message);
|
|
151
|
-
const user = options.user?.auth?.uid || assistant.request.geolocation.ip;
|
|
152
|
-
const responseFormat = options.response === 'json' && !options.model.includes('gpt-3.5')
|
|
153
|
-
? { type: 'json_object' }
|
|
154
|
-
: undefined;
|
|
155
|
-
|
|
156
|
-
// Log
|
|
157
|
-
_log('Prompt', prompt);
|
|
158
|
-
_log('Message', message);
|
|
159
|
-
_log('User', user);
|
|
160
|
-
|
|
161
|
-
// Check for errors
|
|
162
|
-
if (prompt instanceof Error) {
|
|
163
|
-
return reject(assistant.errorify(`Error loading prompt: ${prompt}`, {code: 400}));
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
if (message instanceof Error) {
|
|
167
|
-
return reject(assistant.errorify(`Error loading message: ${message}`, {code: 400}));
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// Format history
|
|
171
|
-
options.history.messages.forEach((m) => {
|
|
172
|
-
m.role = m.role || 'system';
|
|
173
|
-
m.content = (m.content || '').trim();
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
// Request
|
|
177
|
-
function _request(mode, options) {
|
|
178
|
-
return new Promise(async function(resolve, reject) {
|
|
179
|
-
let resultPath = '';
|
|
180
|
-
const request = {
|
|
181
|
-
url: '',
|
|
182
|
-
method: 'post',
|
|
183
|
-
response: 'json',
|
|
184
|
-
// log: true,
|
|
185
|
-
tries: 1,
|
|
186
|
-
timeout: options.timeout,
|
|
187
|
-
headers: {
|
|
188
|
-
'Authorization': `Bearer ${Manager.config.openai.key}`,
|
|
189
|
-
},
|
|
190
|
-
body: {},
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
if (mode === 'chatgpt') {
|
|
194
|
-
request.url = 'https://api.openai.com/v1/chat/completions';
|
|
195
|
-
options.history.messages = options.history.messages.slice(-options.history.limit);
|
|
196
|
-
options.history.messages.unshift({
|
|
197
|
-
role: 'system',
|
|
198
|
-
content: prompt,
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
// Set last history item
|
|
202
|
-
const lastHistory = options.history.messages[options.history.messages.length - 1];
|
|
203
|
-
|
|
204
|
-
// If message is different than last message in history, add it
|
|
205
|
-
if (lastHistory?.content !== message) {
|
|
206
|
-
options.history.messages.push({
|
|
207
|
-
role: 'user',
|
|
208
|
-
content: message,
|
|
209
|
-
});
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
// Log message
|
|
213
|
-
_log('Messages', options.history.messages);
|
|
214
|
-
|
|
215
|
-
request.body = {
|
|
216
|
-
model: options.model,
|
|
217
|
-
response_format: responseFormat,
|
|
218
|
-
messages: options.history.messages,
|
|
219
|
-
temperature: options.temperature,
|
|
220
|
-
max_tokens: options.maxTokens,
|
|
221
|
-
user: user,
|
|
222
|
-
}
|
|
223
|
-
resultPath = 'choices[0].message.content';
|
|
224
|
-
} else if (mode === 'moderation') {
|
|
225
|
-
request.url = 'https://api.openai.com/v1/moderations';
|
|
226
|
-
request.body = {
|
|
227
|
-
input: message,
|
|
228
|
-
user: user,
|
|
229
|
-
}
|
|
230
|
-
resultPath = 'results[0]';
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
// Request
|
|
234
|
-
await fetch(request.url, request)
|
|
235
|
-
.then((r) => {
|
|
236
|
-
// Set token counts
|
|
237
|
-
self.tokens.total.count += r?.usage?.total_tokens || 0;
|
|
238
|
-
self.tokens.input.count += r?.usage?.prompt_tokens || 0;
|
|
239
|
-
self.tokens.output.count += r?.usage?.completion_tokens || 0;
|
|
240
|
-
|
|
241
|
-
// Set token prices
|
|
242
|
-
self.tokens.total.price = (self.tokens.total.count / 1000) * TOKEN_COST_TABLE[options.model].input;
|
|
243
|
-
self.tokens.input.price = (self.tokens.input.count / 1000) * TOKEN_COST_TABLE[options.model].input;
|
|
244
|
-
self.tokens.output.price = (self.tokens.output.count / 1000) * TOKEN_COST_TABLE[options.model].output;
|
|
245
|
-
|
|
246
|
-
return resolve(_.get(r, resultPath));
|
|
247
|
-
})
|
|
248
|
-
.catch((e) => {
|
|
249
|
-
return reject(e);
|
|
250
|
-
})
|
|
251
|
-
});
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
// Moderate if needed
|
|
255
|
-
let moderation = null;
|
|
256
|
-
if (options.moderate) {
|
|
257
|
-
moderation = await _request('moderation', options)
|
|
258
|
-
.then(async (r) => {
|
|
259
|
-
_log('Moderated', r);
|
|
260
|
-
|
|
261
|
-
return r;
|
|
262
|
-
})
|
|
263
|
-
.catch((e) => e);
|
|
264
|
-
|
|
265
|
-
// Check for moderation flag
|
|
266
|
-
if (moderation.flagged) {
|
|
267
|
-
return reject(assistant.errorify(`This request is inappropriate`, {code: 451}));
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
function _attempt() {
|
|
272
|
-
const retries = options.retries;
|
|
273
|
-
const triggers = options.retryTriggers;
|
|
274
|
-
|
|
275
|
-
// Increment attempt
|
|
276
|
-
attempt++;
|
|
277
|
-
|
|
278
|
-
// Log
|
|
279
|
-
_log(`Request ${attempt}/${retries}`);
|
|
280
|
-
|
|
281
|
-
// Request
|
|
282
|
-
_request('chatgpt', options)
|
|
283
|
-
.then((r) => {
|
|
284
|
-
_log('Response', r);
|
|
285
|
-
|
|
286
|
-
// Try to parse JSON response if needed
|
|
287
|
-
try {
|
|
288
|
-
const content = options.response === 'json' ? JSON5.parse(r) : r;
|
|
289
|
-
|
|
290
|
-
// Return
|
|
291
|
-
return resolve({
|
|
292
|
-
content: content,
|
|
293
|
-
tokens: self.tokens,
|
|
294
|
-
moderation: moderation,
|
|
295
|
-
})
|
|
296
|
-
} catch (e) {
|
|
297
|
-
assistant.error('Error parsing response', r, e);
|
|
298
|
-
|
|
299
|
-
// Retry
|
|
300
|
-
if (attempt < retries && triggers.includes('parse')) {
|
|
301
|
-
return _attempt();
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
// Return
|
|
305
|
-
return reject(e);
|
|
306
|
-
}
|
|
307
|
-
})
|
|
308
|
-
.catch((e) => {
|
|
309
|
-
assistant.error('Error requesting', e);
|
|
310
|
-
|
|
311
|
-
// Retry
|
|
312
|
-
if (attempt < retries && triggers.includes('network')) {
|
|
313
|
-
return _attempt();
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
// Return
|
|
317
|
-
return reject(e);
|
|
318
|
-
});
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
// Make attempt
|
|
322
|
-
_attempt();
|
|
323
|
-
});
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
module.exports = OpenAI;
|