utilitas 1995.2.37 → 1995.2.39

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/index.mjs CHANGED
@@ -13,7 +13,6 @@ import * as dbio from './lib/dbio.mjs';
13
13
  import * as email from './lib/email.mjs';
14
14
  import * as encryption from './lib/encryption.mjs';
15
15
  import * as event from './lib/event.mjs';
16
- import * as hal from './lib/hal.mjs';
17
16
  import * as image from './lib/image.mjs';
18
17
  import * as media from './lib/media.mjs';
19
18
  import * as memory from './lib/memory.mjs';
@@ -41,8 +40,8 @@ export {
41
40
  fileType, math, uuid,
42
41
  // features
43
42
  alan, bot, boxes, cache, callosum, color, dbio, email, encryption, event,
44
- hal, image, manifest, media, memory, network, sentinel, shekel, shell, shot,
45
- sms, speech, ssl, storage, tape, uoid, utilitas, vision, web
43
+ image, manifest, media, memory, network, sentinel, shekel, shell, shot, sms,
44
+ speech, ssl, storage, tape, uoid, utilitas, vision, web
46
45
  };
47
46
 
48
47
  if (utilitas.inBrowser() && !globalThis.utilitas) {
package/lib/alan.mjs CHANGED
@@ -1,8 +1,12 @@
1
1
  import { convert } from './storage.mjs';
2
2
  import { create as createUoid } from './uoid.mjs';
3
- import { ensureString, ignoreErrFunc, need, throwError } from './utilitas.mjs';
4
3
  import { loop, end } from './event.mjs';
5
4
 
5
+ import {
6
+ ensureString, ignoreErrFunc, log as _log, need, renderText as _renderText,
7
+ throwError,
8
+ } from './utilitas.mjs';
9
+
6
10
  const _NEED = [
7
11
  '@google-cloud/aiplatform', '@google-cloud/vertexai',
8
12
  '@google/generative-ai', 'js-tiktoken', 'OpenAI',
@@ -24,7 +28,7 @@ const [tool, provider, messages, text] = [
24
28
  messages => ({ messages }), text => ({ text }),
25
29
  ];
26
30
 
27
- const [name, user, system, assistant, model]
31
+ const [name, user, system, assistant, MODEL]
28
32
  = ['Alan', 'user', 'system', 'assistant', 'model'];
29
33
  const [CODE_INTERPRETER, RETRIEVAL, FUNCTION]
30
34
  = ['code_interpreter', 'retrieval', 'function'].map(tool);
@@ -33,15 +37,19 @@ const [NOT_INIT, INVALID_FILE]
33
37
  const [silent, STREAM, instructions]
34
38
  = [true, 'STREAM', 'You are a helpful assistant.'];
35
39
  const GOOGLE_APPLICATION_CREDENTIALS = 'GOOGLE_APPLICATION_CREDENTIALS';
36
- const [tokenRatio, tokenSafeRatio, GPT_QUERY_LIMIT] = [100 / 75, 1.1, 100]; // https://platform.openai.com/tokenizer
40
+ const [tokenRatio, tokenSafeRatio, GPT_QUERY_LIMIT, minsOfDay]
41
+ = [100 / 75, 1.1, 100, 60 * 24]; // https://platform.openai.com/tokenizer
37
42
  const tokenSafe = count => Math.ceil(count * tokenSafeRatio);
38
- const countTokens = t => tokenSafe(t.split(/[^a-z0-9]/i).length * tokenRatio);
39
43
  const clients = {};
40
- const _log = { log: true };
44
+ const LOG = { log: true };
41
45
  const sessionType = `${name.toUpperCase()}-SESSION`;
42
46
  const unifyProvider = options => unifyType(options?.provider, 'AI provider');
43
47
  const unifyEngine = options => unifyType(options?.engine, 'AI engine');
44
48
  const packResp = _text => [text(_text)];
49
+ const trimTailing = text => text.replace(/[\.\s]*$/, '');
50
+ const newSessionId = () => createUoid({ type: sessionType });
51
+ const renderText = (t, o) => _renderText(t, { extraCodeBlock: 0, ...o || {} });
52
+ const log = (cnt, opt) => _log(cnt, import.meta.url, { time: 1, ...opt || {} });
45
53
 
46
54
  const [
47
55
  OPENAI, VERTEX, GEMINI, CHATGPT, ASSISTANT, OPENAI_EMBEDDING,
@@ -70,6 +78,8 @@ const chatConfig = {
70
78
 
71
79
  // https://platform.openai.com/docs/models/continuous-model-upgrades
72
80
  // https://platform.openai.com/account/limits // Tier 2
81
+ // https://cloud.google.com/vertex-ai/docs/generative-ai/model-reference/gemini
82
+ // https://cloud.google.com/vertex-ai/docs/generative-ai/learn/models
73
83
  const MODELS = {
74
84
  [GPT_35_TURBO]: {
75
85
  contextWindow: 4096,
@@ -79,7 +89,7 @@ const MODELS = {
79
89
  },
80
90
  [GPT_35_TURBO_1106]: {
81
91
  contextWindow: 16385,
82
- maximumOutputTokens: 4096,
92
+ maxOutputTokens: 4096,
83
93
  tokenLimitsTPM: 160000,
84
94
  requestLimitsRPM: 5000,
85
95
  trainingData: 'Sep 2021',
@@ -92,7 +102,7 @@ const MODELS = {
92
102
  },
93
103
  [GPT_4_1106]: {
94
104
  contextWindow: 128000,
95
- maximumOutputTokens: 4096,
105
+ maxOutputTokens: 4096,
96
106
  tokenLimitsTPM: 300000,
97
107
  tokenLimitsTPD: 5000000,
98
108
  requestLimitsRPM: 5000,
@@ -100,12 +110,30 @@ const MODELS = {
100
110
  },
101
111
  [GPT_4_VISION]: {
102
112
  contextWindow: 128000,
103
- maximumOutputTokens: 4096,
113
+ maxOutputTokens: 4096,
104
114
  tokenLimitsTPM: 40000,
105
115
  requestLimitsRPM: 120,
106
116
  requestLimitsRPD: 1500,
107
117
  trainingData: 'Apr 2023',
108
118
  },
119
+ [GEMINI_PRO]: {
120
+ contextWindow: 32760,
121
+ maxOutputTokens: 8192,
122
+ trainingData: 'Feb 2023',
123
+ },
124
+ [GEMINI_PRO_VISION]: {
125
+ contextWindow: 16384,
126
+ maxOutputTokens: 2048,
127
+ maxImageSize: Infinity,
128
+ maxFileSize: 20 * 1024 * 1024, // 20 MB
129
+ maxImagePerPrompt: 16,
130
+ maxVideoLength: 60 * 2,
131
+ trainingData: 'Feb 2023',
132
+ supportedMimeTypes: [
133
+ 'image/png', 'image/jpeg', 'video/mov', 'video/mpeg', 'video/mp4',
134
+ 'video/mpg', 'video/avi', 'video/wmv', 'video/mpegps', 'video/flv',
135
+ ],
136
+ },
109
137
  [TEXT_EMBEDDING_ADA_002]: {
110
138
  contextWindow: 5000000,
111
139
  requestLimitsRPM: 5000,
@@ -116,19 +144,17 @@ const MODELS = {
116
144
  for (const n in MODELS) {
117
145
  MODELS[n]['name'] = n;
118
146
  if ([TEXT_EMBEDDING_ADA_002].includes(n)) { continue; }
119
- MODELS[n].maximumOutputTokens
120
- = MODELS[n].maximumOutputTokens
147
+ MODELS[n].maxOutputTokens = MODELS[n].maxOutputTokens
121
148
  || Math.ceil(MODELS[n].contextWindow * 0.4);
122
- MODELS[n].maximumInputTokens
123
- = MODELS[n].maximumInputTokens
124
- || (MODELS[n].contextWindow - MODELS[n].maximumOutputTokens);
125
- MODELS[n].tokenLimitsTPD
126
- = MODELS[n].tokenLimitsTPD || (MODELS[n].tokenLimitsTPM * 60 * 24);
127
- MODELS[n].requestLimitsRPD
128
- = MODELS[n].requestLimitsRPD || (MODELS[n].requestLimitsRPM * 60 * 24);
149
+ MODELS[n].maxInputTokens = MODELS[n].maxInputTokens
150
+ || (MODELS[n].contextWindow - MODELS[n].maxOutputTokens);
151
+ MODELS[n].tokenLimitsTPD = MODELS[n].tokenLimitsTPD
152
+ || (MODELS[n].tokenLimitsTPM * minsOfDay);
153
+ MODELS[n].requestLimitsRPD = MODELS[n].requestLimitsRPD
154
+ || (MODELS[n].requestLimitsRPM * minsOfDay);
129
155
  MODELS[n].requestCapacityRPM = Math.ceil(Math.min(
130
- MODELS[n].tokenLimitsTPM / MODELS[n].maximumInputTokens,
131
- MODELS[n].requestLimitsRPM, MODELS[n].requestLimitsRPD / 60 / 24
156
+ MODELS[n].tokenLimitsTPM / MODELS[n].maxInputTokens,
157
+ MODELS[n].requestLimitsRPM, MODELS[n].requestLimitsRPD / minsOfDay
132
158
  ));
133
159
  }
134
160
 
@@ -144,14 +170,14 @@ const init = async (options) => {
144
170
  case OPENAI:
145
171
  if (options?.apiKey) {
146
172
  const OpenAI = await need('openai');
147
- const { getEncoding } = await need('js-tiktoken');
173
+ // const { getEncoding } = await need('js-tiktoken');
148
174
  const openai = new OpenAI(options);
149
175
  clients[provider] = {
150
176
  client: openai, clientBeta: openai.beta,
151
- tokeniser: getEncoding(options?.tokenModel || 'cl100k_base'),
152
- countTokens: text => tokenSafe(
153
- clients[provider].tokeniser.encode(text).length
154
- ),
177
+ // tokeniser: getEncoding(options?.tokenModel || 'cl100k_base'),
178
+ // countTokens: text => tokenSafe(
179
+ // clients[provider].tokeniser.encode(text).length
180
+ // ),
155
181
  };
156
182
  }
157
183
  break;
@@ -160,17 +186,21 @@ const init = async (options) => {
160
186
  process.env[GOOGLE_APPLICATION_CREDENTIALS] = options.credentials;
161
187
  const { VertexAI } = await need('@google-cloud/vertexai');
162
188
  const aiplatform = await need('@google-cloud/aiplatform');
163
- const [project, location]
164
- = [options?.project, options?.location || 'us-east4'];
189
+ const [project, location] = [
190
+ options?.project,
191
+ options?.location || 'northamerica-northeast1', // 'us-east4'
192
+ ];
165
193
  clients[provider] = {
166
- project, location, helpers: aiplatform.helpers, countTokens,
194
+ project, location, helpers: aiplatform.helpers, // countTokens,
167
195
  generative: (new VertexAI({
168
196
  project, location,
169
197
  })).preview.getGenerativeModel({
170
198
  model: options?.model || DEFAULT_MODELS[VERTEX],
171
199
  generation_config: {
172
- max_output_tokens: 2048, temperature: 0.4, top_p: 1,
173
- top_k: 32, ...options?.generation_config || {},
200
+ // https://cloud.google.com/vertex-ai/docs/generative-ai/model-reference/gemini
201
+ // max_output_tokens: 2048, temperature: 0.4,
202
+ // top_p: 1, top_k: 32,
203
+ ...options?.generation_config || {},
174
204
  },
175
205
  }),
176
206
  prediction: new aiplatform.v1.PredictionServiceClient({
@@ -190,7 +220,7 @@ const init = async (options) => {
190
220
  embedding: genAi.getGenerativeModel({
191
221
  model: DEFAULT_MODELS[GEMINI_EMEDDING],
192
222
  }),
193
- countTokens,
223
+ // countTokens,
194
224
  };
195
225
  }
196
226
  break;
@@ -201,6 +231,10 @@ const init = async (options) => {
201
231
  return clients[provider];
202
232
  };
203
233
 
234
+ const countTokens = input => tokenSafe((
235
+ String.isString(input) ? input : JSON.stringify(input)
236
+ ).split(/[^a-z0-9]/i).length * tokenRatio);
237
+
204
238
  const buildGptMessage = (content, options) => {
205
239
  assert(content, 'Content is required.');
206
240
  return String.isString(content) ? {
@@ -250,7 +284,7 @@ const promptChatGPT = async (content, options) => {
250
284
  };
251
285
  await ignoreErrFunc(async () => await options?.stream?.(
252
286
  packGptResp(chunk, options)
253
- ), _log);
287
+ ), LOG);
254
288
  }
255
289
  return { response: packGptResp(chunk, options) };
256
290
  };
@@ -360,7 +394,7 @@ const promptAssistant = async (content, options) => {
360
394
  const resp = await getRun(thread.id, objRun.id);
361
395
  await ignoreErrFunc(async () => await options?.stream?.(
362
396
  options?.raw ? resp : packResp('')
363
- ), _log);
397
+ ), LOG);
364
398
  if (resp?.status !== 'completed') { return; }
365
399
  resolve(resp);
366
400
  } catch (err) {
@@ -454,7 +488,7 @@ const handleGeminiResponse = async (resp, options) => {
454
488
  for await (const chunk of _resp.stream) {
455
489
  await ignoreErrFunc(async () => await options.stream(
456
490
  options?.raw ? chunk : chunk.candidates[0].content.parts
457
- ), _log);
491
+ ), LOG);
458
492
  }
459
493
  }
460
494
  const result = await _resp.response;
@@ -616,6 +650,8 @@ const initChat = async (options) => {
616
650
  assert(model, `Invalid chat model: '${i}'.`);
617
651
  chatConfig.engines[key] = options.engines[i];
618
652
  chatConfig.engines[key].model = chatConfig.engines[key].model || model;
653
+ const mxPmpt = MODELS[chatConfig.engines[key].model].maxInputTokens / 2;
654
+ let pmptTokens = 0;
619
655
  switch (key) {
620
656
  case ASSISTANT:
621
657
  chatConfig.engines[key].assistantId
@@ -628,62 +664,108 @@ const initChat = async (options) => {
628
664
  }
629
665
  })).id;
630
666
  break;
667
+ case CHATGPT: case GEMINI: case VERTEX:
668
+ pmptTokens = countTokens([buildVertexMessage(
669
+ chatConfig.systemPrompt, { role: system }
670
+ )]); // Use vertex instead of chatgpt because of the longer pack
671
+ assert(
672
+ pmptTokens < mxPmpt,
673
+ `System prompt is too long: ${pmptTokens} / ${mxPmpt} tokens.`
674
+ );
675
+ break;
631
676
  }
632
677
  }
633
678
  return chatConfig;
634
679
  };
635
680
 
681
+ const defaultSession = session => ({
682
+ messages: [], systemPrompt: chatConfig.systemPrompt,
683
+ threadId: null, ...session || {},
684
+ });
685
+
686
+ const assertSessionId = sessionId => {
687
+ sessionId = ensureString(sessionId, { case: 'UP' });
688
+ assert(sessionId, 'Session ID is required.');
689
+ return sessionId;
690
+ };
691
+
636
692
  const getSession = async (sessionId, options) => {
637
- const [session, messages] = [{
638
- messages: [], systemPrompt: chatConfig.systemPrompt, threadId: null,
639
- ...(await chatConfig.sessions.get(
640
- sessionId, options?.prompt, options
641
- )) || {},
642
- }, []];
643
- switch (options?.engine) {
644
- case CHATGPT:
645
- session.messages.map(x => {
646
- messages.push(buildGptMessage(x.request, { role: user }));
647
- messages.push(buildGptMessage(x.response, { role: assistant }));
648
- });
649
- break;
650
- case GEMINI:
651
- case VERTEX:
652
- session.messages.map(x => {
653
- messages.push(buildVertexMessage(x.request, { role: user }));
654
- messages.push(buildVertexMessage(x.response, { role: model }));
655
- });
656
- break;
657
- }
658
- messages.length && (session.messages = messages);
659
- return session;
693
+ sessionId = assertSessionId(sessionId);
694
+ return defaultSession(await chatConfig.sessions.get(
695
+ sessionId, options?.prompt, options
696
+ ));
660
697
  };
661
698
 
662
- const setSessions = async (sessionId, session, options) => {
699
+ const setSession = async (sessionId, session, options) => {
700
+ sessionId = assertSessionId(sessionId);
663
701
  return await chatConfig.sessions.set(sessionId, session, options);
664
702
  };
665
703
 
704
+ const resetSession = async (sessionId, options) => {
705
+ const session = {
706
+ ...defaultSession(),
707
+ ...options?.systemPrompt ? { systemPrompt: options.systemPrompt } : {},
708
+ };
709
+ return await setSession(sessionId, session);
710
+ };
711
+
666
712
  const talk = async (input, options) => {
667
713
  const engine = unifyEngine({ engine: CHATGPT, ...options });
668
714
  assert(chatConfig.engines[engine], NOT_INIT);
669
- const sessionId = options?.sessionId || createUoid({ type: sessionType });
670
- const session = await getSession(sessionId, options);
671
- const chat = { request: input };
672
- let [_provider, _model, resp] = [engine, null, null];
715
+ const model = MODELS[chatConfig.engines[engine].model];
716
+ const sessionId = options?.sessionId || newSessionId();
717
+ const session = await getSession(sessionId, { engine, ...options });
718
+ let [_provider, _model, resp, sys, messages, msgBuilder] =
719
+ [engine, null, null, [], [], null];
673
720
  switch (engine) {
674
721
  case CHATGPT:
675
- _provider = OPENAI; _model = DEFAULT_MODELS[CHATGPT]; break;
722
+ _provider = OPENAI;
723
+ _model = DEFAULT_MODELS[CHATGPT];
724
+ sys.push(buildGptMessage(session.systemPrompt, { role: system }));
725
+ msgBuilder = () => {
726
+ messages = [];
727
+ session.messages.map(x => {
728
+ messages.push(buildGptMessage(x.request, { role: user }));
729
+ messages.push(buildGptMessage(x.response, { role: assistant }));
730
+ });
731
+ };
732
+ msgBuilder()
733
+ break;
676
734
  case ASSISTANT:
677
- _provider = OPENAI; _model = DEFAULT_MODELS[ASSISTANT]; break;
735
+ _provider = OPENAI;
736
+ _model = DEFAULT_MODELS[ASSISTANT];
737
+ break;
678
738
  case GEMINI: case VERTEX:
679
- _model = DEFAULT_MODELS[engine]; break;
739
+ _model = DEFAULT_MODELS[engine];
740
+ sys.push(buildVertexMessage(session.systemPrompt, { role: user }));
741
+ msgBuilder = () => {
742
+ messages = [];
743
+ session.messages.map(x => {
744
+ messages.push(buildVertexMessage(x.request, { role: user }));
745
+ messages.push(buildVertexMessage(x.response, { role: MODEL }));
746
+ });
747
+ };
748
+ msgBuilder()
749
+ break;
680
750
  default:
681
751
  throwError(`Invalid AI engine: '${engine}'.`);
682
752
  }
753
+ while (countTokens([
754
+ ...sys, ...messages, buildVertexMessage(input, { role: user })
755
+ ]) >= model.maxInputTokens) {
756
+ if (messages.length) {
757
+ session.messages.shift();
758
+ msgBuilder && msgBuilder();
759
+ } else {
760
+ input = trimTailing(trimTailing(input).slice(0, -1)) + '...';
761
+ }
762
+ }
763
+ const chat = { request: input };
764
+ log(`Prompt: ${JSON.stringify(input)}`);
683
765
  switch (engine) {
684
766
  case CHATGPT:
685
767
  resp = await promptChatGPT(input, {
686
- messages: session.messages, model: _model, ...options,
768
+ messages, model: _model, ...options,
687
769
  });
688
770
  break;
689
771
  case ASSISTANT:
@@ -694,14 +776,10 @@ const talk = async (input, options) => {
694
776
  session.threadId = resp.thread.id;
695
777
  break;
696
778
  case GEMINI:
697
- resp = await promptGemini(input, {
698
- messages: session.messages, ...options,
699
- });
779
+ resp = await promptGemini(input, { messages, ...options });
700
780
  break;
701
781
  case VERTEX:
702
- resp = await promptVertex(input, {
703
- messages: session.messages, ...options,
704
- });
782
+ resp = await promptVertex(input, { messages, ...options });
705
783
  break;
706
784
  }
707
785
  for (const _resp of resp.response) {
@@ -711,8 +789,24 @@ const talk = async (input, options) => {
711
789
  }
712
790
  }
713
791
  session.messages.push(chat);
714
- await setSessions(sessionId, session, options);
715
- return { sessionId, response: resp.response };
792
+ await setSession(sessionId, session, options);
793
+ const text = resp.response.filter(x => x.text).map(x => x.text).join('\n\n');
794
+ log(`Response: ${JSON.stringify(resp.response)}`);
795
+ return {
796
+ sessionId, response: resp.response, text, rendered: renderText(text),
797
+ spoken: renderText(text, { noCode: true }).replace(/\[\^\d\^\]/ig, ''),
798
+ };
799
+ };
800
+
801
+ const getMaxChatPromptLimit = (options) => {
802
+ let resp = 0;
803
+ for (const i in chatConfig.engines) {
804
+ if (options?.engine && i !== options.engine) { continue; }
805
+ const maxInputTokens = MODELS[chatConfig.engines[i].model].maxInputTokens;
806
+ resp = resp ? Math.min(resp, maxInputTokens) : maxInputTokens;
807
+ }
808
+ assert(resp > 0, 'Chat engine has not been initialized.');
809
+ return resp;
716
810
  };
717
811
 
718
812
  export default init;
@@ -738,11 +832,11 @@ export {
738
832
  buildGptTrainingCase,
739
833
  buildGptTrainingCases,
740
834
  cancelGptFineTuningJob,
835
+ countTokens,
741
836
  createAssistant,
742
837
  createGeminiEmbedding,
743
838
  createGptFineTuningJob,
744
839
  createMessage,
745
- initChat,
746
840
  createOpenAIEmbedding,
747
841
  createVertexEmbedding,
748
842
  deleteAllFilesFromAssistant,
@@ -756,9 +850,11 @@ export {
756
850
  getAssistant,
757
851
  getGptFineTuningJob,
758
852
  getLatestMessage,
853
+ getMaxChatPromptLimit,
759
854
  getRun,
760
855
  getThread,
761
856
  init,
857
+ initChat,
762
858
  listAssistant,
763
859
  listAssistantFiles,
764
860
  listFiles,
@@ -771,6 +867,7 @@ export {
771
867
  promptChatGPT,
772
868
  promptGemini,
773
869
  promptVertex,
870
+ resetSession,
774
871
  run,
775
872
  tailGptFineTuningEvents,
776
873
  talk,
package/lib/manifest.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  const manifest = {
2
2
  "name": "utilitas",
3
3
  "description": "Just another common utility for JavaScript.",
4
- "version": "1995.2.37",
4
+ "version": "1995.2.39",
5
5
  "private": false,
6
6
  "homepage": "https://github.com/Leask/utilitas",
7
7
  "main": "index.mjs",
@@ -36,7 +36,6 @@ const manifest = {
36
36
  "@mozilla/readability": "^0.4.4",
37
37
  "@ngrok/ngrok": "^0.9.1",
38
38
  "@sentry/node": "^7.88.0",
39
- "@waylaidwanderer/chatgpt-api": "^1.37.3",
40
39
  "acme-client": "^5.0.0",
41
40
  "browserify-fs": "^1.0.0",
42
41
  "buffer": "^6.0.3",
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "utilitas",
3
3
  "description": "Just another common utility for JavaScript.",
4
- "version": "1995.2.37",
4
+ "version": "1995.2.39",
5
5
  "private": false,
6
6
  "homepage": "https://github.com/Leask/utilitas",
7
7
  "main": "index.mjs",
@@ -47,7 +47,6 @@
47
47
  "@mozilla/readability": "^0.4.4",
48
48
  "@ngrok/ngrok": "^0.9.1",
49
49
  "@sentry/node": "^7.88.0",
50
- "@waylaidwanderer/chatgpt-api": "^1.37.3",
51
50
  "acme-client": "^5.0.0",
52
51
  "browserify-fs": "^1.0.0",
53
52
  "buffer": "^6.0.3",
package/lib/hal.mjs DELETED
@@ -1,139 +0,0 @@
1
- import {
2
- ensureString, insensitiveCompare, log as _log, need,
3
- renderText as _renderText, throwError, verifyUrl,
4
- } from './utilitas.mjs';
5
-
6
- const _NEED = ['@waylaidwanderer/chatgpt-api'];
7
- const [BING, CHATGPT] = ['BING', 'CHATGPT'];
8
- const renderText = (t, o) => _renderText(t, { extraCodeBlock: 1, ...o || {} });
9
- const log = content => _log(content, import.meta.url);
10
- const iCmp = (strA, strB) => insensitiveCompare(strA, strB, { w: true });
11
- const link = (text, url) => `[${text}](${url})`;
12
- const li = (id, text, url) => `\n${id}. ` + (url ? link(text, url) : text);
13
- const cardReg = /^\[\d*\]:\ ([^\ ]*)\ "(.*)"$/ig;
14
- // https://help.openai.com/en/articles/4936856-what-are-tokens-and-how-to-count-them
15
- // Adding 10% to the token count to account for the model's error margin.
16
- const countTokens = t => Math.ceil(t.split(/[^a-z0-9]/i).length * 100 / 75 * 1.1);
17
- // Keep this for GPT4 {
18
- // const MAX_CONTEXT_TOKENS = 8192;
19
- // }
20
- const MAX_CONTEXT_TOKENS = 4096;
21
- const MAX_PROMPT_TOKENS = Math.floor(MAX_CONTEXT_TOKENS * 0.6);
22
- const MAX_RESPONSE_TOKENS = MAX_CONTEXT_TOKENS - MAX_PROMPT_TOKENS;
23
-
24
- const init = async options => {
25
- const clear = key => key ? (delete sessions[key]) : (sessions = {});
26
- const get = (k, s) => k ? (s ? sessions[k]?.[s] : sessions[k]) : sessions;
27
- const set = (k, s, v) => sessions[k][s] = v;
28
- let [sessions, provider, engine, client] = [{}];
29
- switch ((provider = ensureString(options?.provider, { case: 'UP' }))) {
30
- case BING:
31
- // https://github.com/waylaidwanderer/node-chatgpt-api/blob/main/demos/use-bing-client.js
32
- engine = (await need('@waylaidwanderer/chatgpt-api')).BingAIClient;
33
- client = new engine(options?.clientOptions);
34
- break;
35
- case CHATGPT:
36
- // https://github.com/waylaidwanderer/node-chatgpt-api/blob/main/demos/use-client.js
37
- // Throttled: Request is throttled.
38
- // https://github.com/waylaidwanderer/node-chatgpt-api/issues/96
39
- engine = (await need('@waylaidwanderer/chatgpt-api')).ChatGPTClient;
40
- client = new engine(options?.clientOptions?.apiKey, {
41
- keepNecessaryMessagesOnly: true,
42
- // Keep this for GPT4 {
43
- // maxContextTokens: MAX_CONTEXT_TOKENS,
44
- // }
45
- modelOptions: {
46
- model: options?.model || 'gpt-3.5-turbo',
47
- // Keep this for GPT4 {
48
- // model: options?.model || 'gpt-4',
49
- // max_tokens: MAX_RESPONSE_TOKENS,
50
- // }
51
- ...options?.clientOptions?.modelOptions || {}
52
- }, ...options?.clientOptions || {},
53
- }, options?.cacheOptions);
54
- break;
55
- default: throwError('Invalid AI provider.', 500);
56
- }
57
- client.provider = provider;
58
- const send = async (message, options, onProgress) => {
59
- const [sessionId, cur, upd, apd] = [
60
- options?.sessionId || '_',
61
- key => get(sessionId, key),
62
- (key, val) => set(sessionId, key, val),
63
- (key, val) => set(sessionId, key, cur(key) + val),
64
- ];
65
- options?.session && (sessions[sessionId] = options.session);
66
- const objSession = { parentMessageId: cur('messageId') };
67
- switch (client.provider) {
68
- case BING:
69
- Object.assign(objSession, {
70
- toneStyle: options?.toneStyle || 'balanced', // or creative, precise
71
- jailbreakConversationId: cur('jailbreakConversationId') || true,
72
- });
73
- break;
74
- case CHATGPT:
75
- Object.assign(objSession, { conversationId: cur('conversationId') });
76
- break;
77
- }
78
- log(`Prompt: ${message}`);
79
- try {
80
- sessions[sessionId] = await client.sendMessage(
81
- message, { ...objSession, ...options || {}, onProgress }
82
- );
83
- } catch (err) {
84
- // @todo: when this happens, just reset hal? not all the project?
85
- log(err);
86
- clear(sessionId);
87
- throwError(err?.message || err, 500);
88
- }
89
- upd('responseRendered', renderText(cur('response')));
90
- // We should use `cur('details')?.spokenText` but it's too short and infoless for now.
91
- upd('spokenText', renderText(cur('response'), { noCode: true }).replace(/\[\^\d\^\]/ig, ''));
92
- const sources = cur('details')?.sourceAttributions || [];
93
- if (sources.length) {
94
- apd('responseRendered', '\n\nSource:');
95
- for (let i in sources) {
96
- const idx = ~~i + 1;
97
- upd('responseRendered', cur('responseRendered').replaceAll(
98
- `[^${idx}^]`, link(`(${idx})`, sources[i].seeMoreUrl)
99
- ) + li(idx, sources[i].providerDisplayName, sources[i].seeMoreUrl));
100
- }
101
- }
102
- const cards = (
103
- cur('details')?.adaptiveCards || []
104
- )[0]?.body[0].text.split('\n\n')[0].split('\n').map(line => ({
105
- providerDisplayName: line.replace(cardReg, '$2'),
106
- seeMoreUrl: line.replace(cardReg, '$1'),
107
- })).filter(card => {
108
- for (let src of sources) {
109
- if (iCmp(src.seeMoreUrl, card.seeMoreUrl)) { return false; }
110
- }
111
- return card.providerDisplayName && verifyUrl(card.seeMoreUrl);
112
- });
113
- if (cards?.length) {
114
- apd('responseRendered', '\n\nLearn more:');
115
- for (let i in cards) {
116
- apd('responseRendered', li(
117
- ~~i + 1, cards[i].providerDisplayName, cards[i].seeMoreUrl
118
- ));
119
- }
120
- }
121
- upd(
122
- 'suggestedResponses',
123
- cur('details')?.suggestedResponses?.map?.(s => s.text) || []
124
- );
125
- // console.log(JSON.stringify(cur()));
126
- return cur();
127
- };
128
- return { clear, client, engine, get, send };
129
- };
130
-
131
- export default init;
132
- export {
133
- _NEED,
134
- MAX_CONTEXT_TOKENS,
135
- MAX_PROMPT_TOKENS,
136
- MAX_RESPONSE_TOKENS,
137
- countTokens,
138
- init,
139
- };