utilitas 1998.2.58 → 1998.2.60

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/lib/alan.mjs CHANGED
@@ -49,25 +49,30 @@ const [
49
49
  OPENAI, GEMINI, CHATGPT, OPENAI_EMBEDDING, GEMINI_EMEDDING, OPENAI_TRAINING,
50
50
  OLLAMA, CLAUDE, GPT_4O_MINI, GPT_4O, GPT_O1, GPT_O3_MINI, GEMINI_20_FLASH,
51
51
  GEMINI_20_FLASH_THINKING, GEMINI_20_PRO, NOVA, EMBEDDING_001, DEEPSEEK_R1,
52
- DEEPSEEK_R1_32B, DEEPSEEK_R1_70B, MD_CODE, CHATGPT_REASONING,
53
- TEXT_EMBEDDING_3_SMALL, TEXT_EMBEDDING_3_LARGE, CLAUDE_35_SONNET,
54
- CLAUDE_35_HAIKU, CLOUD_37_SONNET, AUDIO, WAV, CHATGPT_MINI, ATTACHMENTS,
55
- CHAT, OPENAI_VOICE, MEDIUM, LOW, HIGH, GPT_REASONING_EFFORT, THINK,
56
- THINK_STR, THINK_END, AZURE, TOOLS_STR, TOOLS_END, TOOLS, TEXT, THINKING,
57
- OK, FUNC, GPT_45, REDACTED_THINKING,
52
+ DEEPSEEK_R1_70B, MD_CODE, CHATGPT_REASONING, TEXT_EMBEDDING_3_SMALL,
53
+ TEXT_EMBEDDING_3_LARGE, CLOUD_37_SONNET, AUDIO, WAV, CHATGPT_MINI,
54
+ ATTACHMENTS, CHAT, OPENAI_VOICE, MEDIUM, LOW, HIGH, GPT_REASONING_EFFORT,
55
+ THINK, THINK_STR, THINK_END, AZURE, TOOLS_STR, TOOLS_END, TOOLS, TEXT,
56
+ THINKING, OK, FUNC, GPT_45, REDACTED_THINKING, GEMMA_3_27B, AZURE_OPENAI,
57
+ ANTHROPIC, VERTEX_ANTHROPIC, GEMMA327B, size8k, ais, MAX_TOOL_RECURSION,
58
+ LOG, name, user, system, assistant, MODEL, JSON_OBJECT, TOOL, silent,
59
+ NOT_INIT, INVALID_FILE, tokenSafeRatio, GPT_QUERY_LIMIT, minsOfDay,
58
60
  ] = [
59
61
  'OPENAI', 'GEMINI', 'CHATGPT', 'OPENAI_EMBEDDING', 'GEMINI_EMEDDING',
60
62
  'OPENAI_TRAINING', 'OLLAMA', 'CLAUDE', 'gpt-4o-mini', 'gpt-4o', 'o1',
61
63
  'o3-mini', 'gemini-2.0-flash', 'gemini-2.0-flash-thinking-exp',
62
64
  'gemini-2.0-pro-exp', 'nova', 'embedding-001', 'deepseek-r1',
63
- 'deepseek-r1:32b', 'deepseek-r1:70b', '```', 'CHATGPT_REASONING',
64
- 'text-embedding-3-small', 'text-embedding-3-large',
65
- 'claude-3-5-sonnet-latest', 'claude-3-5-haiku-latest',
66
- 'claude-3-7-sonnet@20250219', 'audio', 'wav', 'CHATGPT_MINI',
67
- '[ATTACHMENTS]', 'CHAT', 'OPENAI_VOICE', 'medium', 'low', 'high',
68
- 'medium', 'think', '<think>', '</think>', 'AZURE', '<tools>',
69
- '</tools>', 'tools', 'text', 'thinking', 'OK', 'function',
70
- 'gpt-4.5-preview', 'redacted_thinking',
65
+ 'deepseek-r1:70b', '```', 'CHATGPT_REASONING', 'text-embedding-3-small',
66
+ 'text-embedding-3-large', 'claude-3-7-sonnet@20250219', 'audio', 'wav',
67
+ 'CHATGPT_MINI', '[ATTACHMENTS]', 'CHAT', 'OPENAI_VOICE', 'medium',
68
+ 'low', 'high', 'medium', 'think', '<think>', '</think>', 'AZURE',
69
+ '<tools>', '</tools>', 'tools', 'text', 'thinking', 'OK', 'function',
70
+ 'gpt-4.5-preview', 'redacted_thinking', 'gemma-3-27b-it',
71
+ 'AZURE OPENAI', 'ANTHROPIC', 'VERTEX ANTHROPIC', 'gemma3:27b',
72
+ 7680 * 4320, {}, 10, { log: true }, 'Alan', 'user', 'system',
73
+ 'assistant', 'model', 'json_object', 'tool', true,
74
+ 'AI engine has not been initialized.', 'Invalid file data.', 1.1, 100,
75
+ 60 * 24,
71
76
  ];
72
77
 
73
78
  const [
@@ -88,45 +93,52 @@ const [tool, provider, messages, text] = [
88
93
  messages => ({ messages }), text => ({ text }),
89
94
  ];
90
95
 
91
- const [name, user, system, assistant, MODEL, JSON_OBJECT, TOOL, silent]
92
- = ['Alan', 'user', 'system', 'assistant', 'model', 'json_object', 'tool', true];
93
96
  const [CODE_INTERPRETER, RETRIEVAL, FUNCTION]
94
97
  = ['code_interpreter', 'retrieval', FUNC].map(tool);
95
- const [NOT_INIT, INVALID_FILE]
96
- = ['AI engine has not been initialized.', 'Invalid file data.'];
97
98
  const chatConfig
98
99
  = { sessions: new Map(), engines: {}, systemPrompt: INSTRUCTIONS };
99
- const [tokenSafeRatio, GPT_QUERY_LIMIT, minsOfDay] = [1.1, 100, 60 * 24];
100
+ const [sessionType, aiType]
101
+ = [`${name.toUpperCase()}-SESSION`, `${name.toUpperCase()}-AI`];
102
+ const [newSessionId, newAiId]
103
+ = [sessionType, aiType].map(type => () => createUoid({ type }));
100
104
  const tokenSafe = count => Math.ceil(count * tokenSafeRatio);
101
105
  const clients = {};
102
- const size8k = 7680 * 4320;
103
- const MAX_TOOL_RECURSION = 10;
104
- const LOG = { log: true };
105
- const sessionType = `${name.toUpperCase()}-SESSION`;
106
106
  const unifyProvider = options => unifyType(options?.provider, 'AI provider');
107
107
  const unifyEngine = options => unifyType(options?.engine, 'AI engine');
108
108
  const trimTailing = text => text.replace(/[\.\s]*$/, '');
109
- const newSessionId = () => createUoid({ type: sessionType });
110
109
  const renderText = (t, o) => _renderText(t, { extraCodeBlock: 0, ...o || {} });
111
110
  const log = (cnt, opt) => _log(cnt, import.meta.url, { time: 1, ...opt || {} });
112
111
  const CONTENT_IS_REQUIRED = 'Content is required.';
113
112
  const assertContent = content => assert(content.length, CONTENT_IS_REQUIRED);
114
113
  const countToolCalls = r => r?.split('\n').filter(x => x === TOOLS_STR).length;
114
+ const assertApiKey = (p, o) => assert(o?.apiKey, `${p} api key is required.`);
115
+ const libOpenAi = async opts => await need('openai', { ...opts, raw: true });
116
+ const OpenAI = async opts => new (await libOpenAi(opts)).OpenAI(opts);
117
+ const AzureOpenAI = async opts => new (await libOpenAi(opts)).AzureOpenAI(opts);
115
118
 
116
119
  const DEFAULT_MODELS = {
117
- [CHATGPT_MINI]: GPT_4O_MINI,
118
- [CHATGPT_REASONING]: GPT_O3_MINI,
119
- [CHATGPT]: GPT_4O,
120
- [CLAUDE]: CLOUD_37_SONNET,
121
- [GEMINI_EMEDDING]: EMBEDDING_001,
120
+ [OPENAI]: GPT_4O,
121
+ [AZURE_OPENAI]: GPT_4O,
122
122
  [GEMINI]: GEMINI_20_FLASH,
123
- [OLLAMA]: DEEPSEEK_R1,
124
- [AZURE]: DEEPSEEK_R1,
123
+ [ANTHROPIC]: CLOUD_37_SONNET,
124
+ [VERTEX_ANTHROPIC]: CLOUD_37_SONNET,
125
+ [OLLAMA]: GEMMA327B,
126
+ [OPENAI_VOICE]: NOVA,
125
127
  [OPENAI_EMBEDDING]: TEXT_EMBEDDING_3_SMALL,
128
+ [GEMINI_EMEDDING]: EMBEDDING_001,
126
129
  [OPENAI_TRAINING]: GPT_4O_MINI, // https://platform.openai.com/docs/guides/fine-tuning
127
- [OPENAI_VOICE]: NOVA,
130
+
131
+
132
+ [CHATGPT_MINI]: GPT_4O_MINI,
133
+ [CHATGPT_REASONING]: GPT_O3_MINI,
128
134
  };
129
135
 
136
+
137
+
138
+
139
+
140
+
141
+
130
142
  DEFAULT_MODELS[CHAT] = DEFAULT_MODELS[GEMINI];
131
143
 
132
144
  const tokenRatioByWords = Math.min(
@@ -194,13 +206,9 @@ const MODELS = {
194
206
  reasoning: true,
195
207
  vision: true,
196
208
  tools: true,
197
- // audio: 'gpt-4o-audio-preview', // fallback to GPT-4O to support audio
198
209
  supportedMimeTypes: [
199
210
  png, jpeg, gif, webp,
200
211
  ],
201
- // supportedAudioTypes: [ // fallback to GPT-4O to support audio
202
- // wav,
203
- // ],
204
212
  },
205
213
  [GPT_O3_MINI]: {
206
214
  contextWindow: 200000,
@@ -214,13 +222,9 @@ const MODELS = {
214
222
  reasoning: true,
215
223
  vision: true,
216
224
  tools: true,
217
- // audio: 'gpt-4o-mini-audio-preview', // fallback to GPT-4O-MINI to support audio
218
225
  supportedMimeTypes: [
219
226
  png, jpeg, gif, webp,
220
227
  ],
221
- // supportedAudioTypes: [ // fallback to GPT-4O-MINI to support audio
222
- // wav,
223
- // ],
224
228
  },
225
229
  [GPT_45]: {
226
230
  contextWindow: 128000,
@@ -305,6 +309,15 @@ const MODELS = {
305
309
  flac, mp3, m4a, mpga, opus, pcm, wav, webm, tgpp,
306
310
  ],
307
311
  },
312
+ [GEMMA_3_27B]: {
313
+ contextWindow: 128 * 1000,
314
+ imageCostTokens: 256,
315
+ maxImageSize: 896 * 896,
316
+ maxOutputTokens: 1024 * 8,
317
+ vision: true,
318
+ json: true,
319
+ supportedMimeTypes: [png, jpeg],
320
+ },
308
321
  [DEEPSEEK_R1]: {
309
322
  contextWindow: 128 * 1000,
310
323
  maxOutputTokens: 32768,
@@ -333,24 +346,6 @@ const MODELS = {
333
346
  embedding: true,
334
347
  requestLimitsRPM: 1500,
335
348
  },
336
- [CLAUDE_35_SONNET]: { // https://docs.anthropic.com/en/docs/about-claude/models
337
- contextWindow: 200 * 1000,
338
- maxOutputTokens: 8192,
339
- imageCostTokens: size8k / 750,
340
- documentCostTokens: 3000 * 100, // 100 pages: https://docs.anthropic.com/en/docs/build-with-claude/pdf-support
341
- maxImagePerPrompt: 5, // https://docs.anthropic.com/en/docs/build-with-claude/vision
342
- maxImageSize: 1092, // by pixels
343
- maxDocumentPages: 100,
344
- maxDocumentFile: 1024 * 1024 * 32, // 32MB
345
- requestLimitsRPM: 50,
346
- tokenLimitsITPM: 40000,
347
- tokenLimitsOTPM: 8000,
348
- trainingData: 'Apr 2024',
349
- tools: true,
350
- supportedMimeTypes: [
351
- png, jpeg, gif, webp, pdf,
352
- ],
353
- },
354
349
  // https://console.cloud.google.com/vertex-ai/publishers/anthropic/model-garden/claude-3-7-sonnet?authuser=5&inv=1&invt=Abqftg&project=backend-alpha-97077
355
350
  [CLOUD_37_SONNET]: {
356
351
  contextWindow: 200 * 1000,
@@ -373,9 +368,8 @@ const MODELS = {
373
368
  },
374
369
  };
375
370
 
376
- MODELS[CLAUDE_35_HAIKU] = MODELS[CLAUDE_35_SONNET];
377
- MODELS[DEEPSEEK_R1_32B] = MODELS[DEEPSEEK_R1];
378
371
  MODELS[DEEPSEEK_R1_70B] = MODELS[DEEPSEEK_R1];
372
+ MODELS[GEMMA327B] = MODELS[GEMMA_3_27B];
379
373
 
380
374
  for (const n in MODELS) {
381
375
  MODELS[n]['name'] = n;
@@ -505,56 +499,90 @@ const toolsGemini = async () => (await toolsOpenAI()).map(x => ({
505
499
  }
506
500
  }));
507
501
 
508
- const init = async (options) => {
502
+ const init = async (options = {}) => {
503
+ const id = newAiId();
509
504
  const provider = unifyProvider(options);
505
+ const modelName = options.model || DEFAULT_MODELS[provider];
506
+ assert(modelName, `Model is required for provider: ${provider}.`);
507
+ let model = options.modelConfig || MODELS[modelName];
508
+ assert(model, `The model has not been configured yet: ${modelName}.`);
509
+ model = { name: modelName, ...model };
510
510
  switch (provider) {
511
- case OPENAI: case AZURE:
512
- if (options?.apiKey) {
513
- provider === AZURE && assert(
514
- options?.baseURL, 'Azure api endpoint is required.'
515
- );
516
- const libOpenAI = await need('openai', { raw: true });
517
- const openai = new (options?.endpoint && options?.deployment
518
- ? libOpenAI.AzureOpenAI : libOpenAI.OpenAI)(options);
519
- clients[provider] = { client: openai, clientBeta: openai.beta };
520
- }
511
+ case OPENAI:
512
+ assertApiKey(provider, options);
513
+ ais[id] = {
514
+ id, provider, model, client: await OpenAI(options),
515
+ prompt: async (cnt, opts) => await promptOpenAI(id, cnt, opts),
516
+ };
517
+ break;
518
+ case AZURE_OPENAI:
519
+ assertApiKey(provider, options);
520
+ assert(options.endpoint,
521
+ `{provider} api endpoint and deployment are required.`);
522
+ ais[id] = {
523
+ id, provider, model, client: await AzureOpenAI({
524
+ apiVersion: '2025-01-01-preview',
525
+ deployment: model.name, ...options,
526
+ }),
527
+ prompt: async (cnt, opts) => await promptOpenAI(id, cnt, opts),
528
+ };
529
+ break;
530
+ case AZURE:
531
+ assertApiKey(provider, options);
532
+ assert(options.baseURL, `${provider} api endpoint is required.`);
533
+ ais[id] = {
534
+ id, provider, model, client: await OpenAI(options),
535
+ prompt: async (cnt, opts) => await promptOpenAI(id, cnt, opts),
536
+ };
521
537
  break;
522
538
  case GEMINI:
523
- if (options?.apiKey) {
524
- const { GoogleGenerativeAI } = await need('@google/generative-ai');
525
- const genAi = new GoogleGenerativeAI(options.apiKey);
526
- clients[provider] = { client: genAi };
527
- }
539
+ assertApiKey(provider, options);
540
+ const { GoogleGenerativeAI } = await need('@google/generative-ai');
541
+ ais[id] = {
542
+ id, provider, model,
543
+ client: new GoogleGenerativeAI(options.apiKey),
544
+ prompt: async (cnt, opts) => await promptGemini(id, cnt, opts),
545
+ };
528
546
  break;
529
- case CLAUDE:
530
- if (options?.apiKey || (options?.credentials && options?.projectId)) {
531
- // https://github.com/anthropics/anthropic-sdk-typescript/tree/main/packages/vertex-sdk
532
- const Anthropic = (await need(options?.credentials
533
- ? '@anthropic-ai/vertex-sdk' : '@anthropic-ai/sdk', { raw: true }))[
534
- options?.credentials ? 'AnthropicVertex' : 'Anthropic'
535
- ];
536
- if (options?.credentials) {
537
- process.env['GOOGLE_APPLICATION_CREDENTIALS'] = options.credentials;
538
- process.env['ANTHROPIC_VERTEX_PROJECT_ID'] = options.projectId;
539
- }
540
- const anthropic = new Anthropic({
541
- ...options?.apiKey ? { apiKey: options.apiKey } : {},
542
- ...options?.credentials ? { region: options?.region || 'us-east5' } : {},
543
- });
544
- clients[provider] = { client: anthropic };
545
- }
547
+ case ANTHROPIC:
548
+ assertApiKey(provider, options);
549
+ const Anthropic = (await need('@anthropic-ai/sdk')).Anthropic;
550
+ ais[id] = {
551
+ id, provider, model, client: new Anthropic(options),
552
+ prompt: async (cnt, opts) => await promptAnthropic(id, cnt, opts),
553
+ };
554
+ break;
555
+ case VERTEX_ANTHROPIC:
556
+ // https://github.com/anthropics/anthropic-sdk-typescript/tree/main/packages/vertex-sdk
557
+ assert(options?.credentials, `${provider} api credentials are required.`);
558
+ const AnthropicVertex = (await need('@anthropic-ai/vertex-sdk')).AnthropicVertex;
559
+ process.env['GOOGLE_APPLICATION_CREDENTIALS'] = options.credentials;
560
+ process.env['ANTHROPIC_VERTEX_PROJECT_ID'] = options.projectId;
561
+ ais[id] = {
562
+ id, provider, model,
563
+ client: new AnthropicVertex({ region: options?.region || 'us-east5' }),
564
+ prompt: async (cnt, opts) => await promptAnthropic(id, cnt, opts),
565
+ };
546
566
  break;
547
567
  case OLLAMA:
548
- clients[provider] || (clients[provider] = {
568
+ ais[id] = {
569
+ id, provider, model,
549
570
  client: new (await need('ollama', { raw: true })).Ollama(options),
550
- model: options?.model || DEFAULT_MODELS[OLLAMA],
551
- });
571
+ prompt: async (cnt, opts) => await promptOllama(id, cnt, opts),
572
+ };
552
573
  break;
553
574
  default:
554
- throwError(`Invalid AI provider: ${options?.provider || 'null'}`);
575
+ throwError(`Invalid AI provider: ${options.provider || 'null'}.`);
555
576
  }
556
- assert(clients[provider], NOT_INIT);
557
- return clients[provider];
577
+ return ais[id];
578
+ };
579
+
580
+ const getAi = async (id, options) => {
581
+ if (id) {
582
+ if (ais[id]) { return options?.client ? ais[id]?.client : ais[id]; }
583
+ else { throwError(`AI not found: ${id}.`); }
584
+ }
585
+ return ais;
558
586
  };
559
587
 
560
588
  const countTokens = async (input, options) => {
@@ -806,7 +834,8 @@ const buildPrompts = async (model, input, options = {}) => {
806
834
  prompt = buildOllamaMessage(content, options);
807
835
  break;
808
836
  case GEMINI:
809
- systemPrompt = buildGeminiHistory(options.systemPrompt, _system);
837
+ const _role = { role: options.model === GEMMA_3_27B ? user : system };
838
+ systemPrompt = buildGeminiHistory(options.systemPrompt, _role);
810
839
  prompt = options.toolsResult?.[options.toolsResult?.length - 1]?.parts
811
840
  || buildGeminiMessage(content, options)
812
841
  break;
@@ -844,6 +873,7 @@ const buildPrompts = async (model, input, options = {}) => {
844
873
  break;
845
874
  case GEMINI:
846
875
  history.push(
876
+ ...options.model === GEMMA_3_27B ? [systemPrompt] : [],
847
877
  ...options.toolsResult?.length ? [
848
878
  buildGeminiHistory(content, { ...options, role: user }),
849
879
  ...options.toolsResult.slice(0, options.toolsResult.length - 1)
@@ -861,6 +891,10 @@ const buildPrompts = async (model, input, options = {}) => {
861
891
  content = trimTailing(trimTailing(content).slice(0, -1)) + '...';
862
892
  }
863
893
  }, model.maxInputTokens - options.attachments?.length * ATTACHMENT_TOKEN_COST);
894
+ if ([CHATGPT, OLLAMA].includes(options.flavor)
895
+ || options.model === GEMMA_3_27B) {
896
+ systemPrompt = null;
897
+ }
864
898
  return { systemPrompt, history, prompt };
865
899
  };
866
900
 
@@ -957,36 +991,32 @@ const mergeMsgs = (resp, calls) => [resp, ...calls.length ? [
957
991
  `⚠️ Tools recursion limit reached: ${MAX_TOOL_RECURSION}`
958
992
  ] : []].map(x => x.trim()).join('\n\n');
959
993
 
960
- const promptChatGPT = async (content, options = {}) => {
961
- if (options.model) { } else if (options.provider === AZURE) {
962
- options.model = DEFAULT_MODELS[AZURE];
963
- } else if (options.reasoning) {
964
- options.model = DEFAULT_MODELS[CHATGPT_REASONING];
965
- } else {
966
- options.model = DEFAULT_MODELS[CHATGPT];
967
- }
968
- let [_MODEL, result, resultAudio, event, resultTools, responded] = [
969
- MODELS[options.model], options?.result ?? '', Buffer.alloc(0), null, [],
970
- false
994
+ const promptOpenAI = async (aiId, content, options = {}) => {
995
+ let { provider, client, model } = await getAi(aiId);
996
+ let [result, resultAudio, event, resultTools, responded, azure] = [
997
+ options.result ?? '', Buffer.alloc(0), null, [], false,
998
+ provider === AZURE
971
999
  ];
972
- options.reasoning && !options.reasoning_effort
973
- && (options.reasoning_effort = GPT_REASONING_EFFORT);
974
- const { client } = await getOpenAIClient(options);
1000
+ options.flavor = CHATGPT;
1001
+ options.model = options.model || model.name;
975
1002
  const { history }
976
- = await buildPrompts(_MODEL, content, { ...options, flavor: CHATGPT });
1003
+ = await buildPrompts(MODELS[options.model], content, options);
1004
+ model = MODELS[options.model];
1005
+ model.reasoning && !azure && !options.reasoning_effort
1006
+ && (options.reasoning_effort = GPT_REASONING_EFFORT);
977
1007
  const modalities = options.modalities
978
1008
  || (options.audioMode ? [TEXT, AUDIO] : undefined);
979
1009
  [options.audioMimeType, options.suffix] = [pcm16, 'pcm.wav'];
980
1010
  const resp = await client.chat.completions.create({
1011
+ model: azure ? undefined : options.model, ...history,
1012
+ ...options.jsonMode ? { response_format: { type: JSON_OBJECT } } : {},
981
1013
  modalities, audio: options.audio || (
982
1014
  modalities?.find?.(x => x === AUDIO)
983
1015
  && { voice: DEFAULT_MODELS[OPENAI_VOICE], format: 'pcm16' }
984
- ), ...history, ..._MODEL?.tools ? {
1016
+ ), ...model.tools && !azure ? {
985
1017
  tools: options.tools ?? (await toolsOpenAI()).map(x => x.def),
986
- } : {}, ...options.jsonMode ? {
987
- response_format: { type: JSON_OBJECT }
988
- } : {}, model: options.model, stream: true,
989
- store: true, tool_choice: 'auto',
1018
+ tool_choice: 'auto',
1019
+ } : {}, ...azure ? {} : { store: true }, stream: true,
990
1020
  });
991
1021
  for await (event of resp) {
992
1022
  event = event?.choices?.[0] || {};
@@ -1001,9 +1031,11 @@ const promptChatGPT = async (content, options = {}) => {
1001
1031
  isSet(x.index, true) && (curFunc.index = x.index);
1002
1032
  x.id && (curFunc.id = x.id);
1003
1033
  x.type && (curFunc.type = x.type);
1004
- curFunc.function || (curFunc.function = { name: '', arguments: '' });
1034
+ curFunc.function
1035
+ || (curFunc.function = { name: '', arguments: '' });
1005
1036
  x?.function?.name && (curFunc.function.name += x.function.name);
1006
- x?.function?.arguments && (curFunc.function.arguments += x.function.arguments);
1037
+ x?.function?.arguments
1038
+ && (curFunc.function.arguments += x.function.arguments);
1007
1039
  }
1008
1040
  options.result && deltaText
1009
1041
  && (responded = responded || (deltaText = `\n\n${deltaText}`));
@@ -1021,25 +1053,27 @@ const promptChatGPT = async (content, options = {}) => {
1021
1053
  };
1022
1054
  const { toolsResult, toolsResponse }
1023
1055
  = await handleToolsCall(event, { ...options, result });
1024
- if (toolsResult.length && countToolCalls(toolsResponse) < MAX_TOOL_RECURSION) {
1025
- return promptChatGPT(content, { ...options, toolsResult, result: toolsResponse });
1056
+ if (toolsResult.length
1057
+ && countToolCalls(toolsResponse) < MAX_TOOL_RECURSION) {
1058
+ return promptOpenAI(aiId, content, {
1059
+ ...options, toolsResult, result: toolsResponse,
1060
+ });
1026
1061
  }
1027
1062
  event.text = mergeMsgs(toolsResponse, toolsResult);
1028
1063
  return await packResp(event, options);
1029
1064
  };
1030
1065
 
1031
- const promptAzure = async (content, options = {}) =>
1032
- await promptChatGPT(content, { ...options, provider: AZURE });
1033
-
1034
- const promptOllama = async (content, options = {}) => {
1035
- const { client, model } = await getOllamaClient(options);
1066
+ const promptOllama = async (aiId, content, options = {}) => {
1067
+ const { client, model } = await getAi(aiId);
1036
1068
  // https://github.com/ollama/ollama-js
1037
1069
  // https://github.com/jmorganca/ollama/blob/main/examples/typescript-simplechat/client.ts
1038
- options.model = options?.model || model;
1039
- let [_MODEL, chunk, result] = [MODELS[options.model], null, ''];
1040
- const { history: h }
1041
- = await buildPrompts(_MODEL, content, { ...options, flavor: OLLAMA });
1042
- const resp = await client.chat({ model: options.model, stream: true, ...h });
1070
+ options.model = options?.model || model.name;
1071
+ let [chunk, result] = [null, ''];
1072
+ const { history }
1073
+ = await buildPrompts(model, content, { ...options, flavor: OLLAMA });
1074
+ const resp = await client.chat({
1075
+ model: options.model, stream: true, ...history,
1076
+ });
1043
1077
  for await (chunk of resp) {
1044
1078
  const delta = chunk.message.content || '';
1045
1079
  result += delta;
@@ -1050,33 +1084,28 @@ const promptOllama = async (content, options = {}) => {
1050
1084
  return await packResp({ text: result }, options);
1051
1085
  };
1052
1086
 
1053
- const promptClaude = async (content, options = {}) => {
1054
- options.model = options.model || DEFAULT_MODELS[CLAUDE];
1087
+ const promptAnthropic = async (aiId, content, options = {}) => {
1088
+ const { client, model } = await getAi(aiId);
1055
1089
  let [
1056
- _MODEL, event, text, thinking, signature, result, thinkEnd, tool_use,
1057
- responded, redacted_thinking,
1058
- ] = [
1059
- MODELS[options.model], null, '', '', '', options.result ?? '', '',
1060
- [], false, [],
1061
- ];
1062
- // https://docs.anthropic.com/en/docs/build-with-claude/extended-thinking
1063
- options?.test_redacted_thinking && !options?.result && (
1090
+ event, text, thinking, signature, result, thinkEnd, tool_use,
1091
+ responded, redacted_thinking
1092
+ ] = [null, '', '', '', options.result ?? '', '', [], false, []];
1093
+ options.model = options.model || model.name;
1094
+ options.test_redacted_thinking && !result && (
1064
1095
  content += '\n\nANTHROPIC_MAGIC_STRING_TRIGGER_REDACTED_THINKING_'
1065
1096
  + '46C9A13E193C177646C7398A98432ECCCE4C1253D5E2D82641AC0E52CC2876CB'
1066
- );
1067
- const { client } = await getClaudeClient(options);
1097
+ ); // https://docs.anthropic.com/en/docs/build-with-claude/extended-thinking
1068
1098
  const { systemPrompt: system, history }
1069
- = await buildPrompts(_MODEL, content, { ...options, flavor: CLAUDE });
1099
+ = await buildPrompts(model, content, { ...options, flavor: CLAUDE });
1070
1100
  const resp = await client.beta.messages.create({
1071
- model: options.model,
1072
- max_tokens: options?.extendedThinking ? 128000 : _MODEL.maxOutputTokens,
1073
- ...history, system, stream: true,
1074
- ...options.reasoning ?? _MODEL?.reasoning ? {
1101
+ model: options.model, ...history, system, stream: true,
1102
+ max_tokens: options.extendedThinking ? 128000 : model.maxOutputTokens,
1103
+ ...(options.reasoning ?? model.reasoning) ? {
1075
1104
  thinking: options.thinking || {
1076
1105
  type: 'enabled',
1077
1106
  budget_tokens: options?.extendedThinking ? 16000 : 1024,
1078
1107
  },
1079
- } : {}, ..._MODEL?.tools ? {
1108
+ } : {}, ...model?.tools ? {
1080
1109
  tools: options.tools ?? (await toolsClaude()).map(x => x.def),
1081
1110
  tool_choice: { type: 'auto' }, betas: [
1082
1111
  // https://docs.anthropic.com/en/docs/build-with-claude/tool-use/token-efficient-tool-use
@@ -1121,7 +1150,7 @@ const promptClaude = async (content, options = {}) => {
1121
1150
  event, { ...options, result, flavor: CLAUDE },
1122
1151
  );
1123
1152
  if (tool_use.length && countToolCalls(toolsResponse) < MAX_TOOL_RECURSION) {
1124
- return await promptClaude(content, {
1153
+ return await promptAnthropic(aiId, content, {
1125
1154
  ...options, toolsResult: [...options.toolsResult || [],
1126
1155
  ...toolsResult], result: toolsResponse,
1127
1156
  });
@@ -1178,31 +1207,29 @@ const packGeminiReferences = (chunks, supports) => {
1178
1207
  return references;
1179
1208
  };
1180
1209
 
1181
- const promptGemini = async (content, options = {}) => {
1182
- options.model || (options.model = DEFAULT_MODELS[GEMINI]);
1183
- let [result, references, functionCalls, responded, _MODEL]
1184
- = [options.result ?? '', null, null, false, MODELS[options.model]];
1185
- const { client: _client } = await getGeminiClient(options);
1210
+ const promptGemini = async (aiId, content, options = {}) => {
1211
+ const { client, model } = await getAi(aiId);
1212
+ let [result, references, functionCalls, responded]
1213
+ = [options.result ?? '', null, null, false];
1214
+ options.model = options.model || model.name;
1186
1215
  const { systemPrompt: systemInstruction, history, prompt }
1187
- = await buildPrompts(_MODEL, content, { ...options, flavor: GEMINI });
1188
- const client = _client.getGenerativeModel({
1216
+ = await buildPrompts(model, content, { ...options, flavor: GEMINI });
1217
+ const _client = client.getGenerativeModel({
1189
1218
  model: options.model, systemInstruction,
1190
- ...MODELS[options.model]?.tools && !options.jsonMode ? (
1191
- options.tools ?? {
1192
- tools: [
1193
- // @todo: Gemini will failed when using these tools together.
1194
- // https://ai.google.dev/gemini-api/docs/function-calling
1195
- // { codeExecution: {} },
1196
- // { googleSearch: {} },
1197
- { functionDeclarations: (await toolsGemini()).map(x => x.def) },
1198
- ],
1199
- toolConfig: { functionCallingConfig: { mode: 'AUTO' } },
1200
- }
1201
- ) : {},
1219
+ ...model?.tools && !options.jsonMode ? (options.tools ?? {
1220
+ tools: [
1221
+ // @todo: Gemini will failed when using these tools together.
1222
+ // https://ai.google.dev/gemini-api/docs/function-calling
1223
+ // { codeExecution: {} },
1224
+ // { googleSearch: {} },
1225
+ { functionDeclarations: (await toolsGemini()).map(x => x.def) },
1226
+ ],
1227
+ toolConfig: { functionCallingConfig: { mode: 'AUTO' } },
1228
+ }) : {},
1202
1229
  });
1203
1230
  // https://github.com/google/generative-ai-js/blob/main/samples/node/advanced-chat.js
1204
1231
  // Google's bug: history is not allowed while using inline_data?
1205
- const chat = client.startChat({ history, ...generationConfig(options) });
1232
+ const chat = _client.startChat({ history, ...generationConfig(options) });
1206
1233
  const resp = await chat.sendMessageStream(prompt);
1207
1234
  for await (const chunk of resp.stream) {
1208
1235
  functionCalls || (functionCalls = chunk.functionCalls);
@@ -1227,10 +1254,11 @@ const promptGemini = async (content, options = {}) => {
1227
1254
  { role: MODEL, parts: functionCalls },
1228
1255
  { ...options, result, flavor: GEMINI }
1229
1256
  );
1230
- if (toolsResult.length && countToolCalls(toolsResponse) < MAX_TOOL_RECURSION) {
1231
- return promptGemini(content, {
1232
- ...options || {}, toolsResult: [...options?.toolsResult || [],
1233
- ...toolsResult], result: toolsResponse,
1257
+ if (toolsResult.length
1258
+ && countToolCalls(toolsResponse) < MAX_TOOL_RECURSION) {
1259
+ return promptGemini(aiId, content, {
1260
+ ...options || {}, result: toolsResponse,
1261
+ toolsResult: [...options?.toolsResult || [], ...toolsResult],
1234
1262
  });
1235
1263
  }
1236
1264
  return await packResp({
@@ -1427,9 +1455,9 @@ const talk = async (input, options) => {
1427
1455
  ...options,
1428
1456
  };
1429
1457
  switch (engine) {
1430
- case CHATGPT: resp = await promptChatGPT(input, pmtOptions); break;
1458
+ case CHATGPT: resp = await promptOpenAI(input, pmtOptions); break;
1431
1459
  case GEMINI: resp = await promptGemini(input, pmtOptions); break;
1432
- case CLAUDE: resp = await promptClaude(input, pmtOptions); break;
1460
+ case CLAUDE: resp = await promptAnthropic(input, pmtOptions); break;
1433
1461
  case OLLAMA: resp = await promptOllama(input, pmtOptions); break;
1434
1462
  case AZURE: resp = await promptAzure(input, pmtOptions); break;
1435
1463
  default: throwError(`Invalid AI engine: '${engine}'.`);
@@ -1553,17 +1581,17 @@ const analyzeSessions = async (sessionIds, options) => {
1553
1581
  };
1554
1582
 
1555
1583
  const PREFERRED_ENGINES = [
1556
- { client: OPENAI, func: promptChatGPT, multimodal: 0 },
1584
+ { client: OPENAI, func: promptOpenAI, multimodal: 0 },
1557
1585
  { client: GEMINI, func: promptGemini, multimodal: 1 },
1558
- { client: CLAUDE, func: promptClaude, multimodal: 2 },
1559
- { client: AZURE, func: promptAzure, multimodal: 3 },
1586
+ { client: CLAUDE, func: promptAnthropic, multimodal: 2 },
1587
+ // { client: AZURE, func: promptAzure, multimodal: 3 },
1560
1588
  { client: OLLAMA, func: promptOllama, multimodal: 99 },
1561
1589
  ]; // keep gpt first to avoid gemini grounding by default
1562
1590
 
1563
1591
  export default init;
1564
1592
  export {
1565
1593
  ATTACHMENT_TOKEN_COST, CLOUD_37_SONNET, CODE_INTERPRETER, DEEPSEEK_R1,
1566
- DEEPSEEK_R1_32B, DEEPSEEK_R1_70B, DEFAULT_MODELS,
1594
+ DEEPSEEK_R1_70B, DEFAULT_MODELS,
1567
1595
  EMBEDDING_001,
1568
1596
  FUNCTION, GEMINI_20_FLASH, GEMINI_20_FLASH_THINKING, GPT_45, GPT_4O, GPT_4O_MINI, GPT_O1, GPT_O3_MINI, INSTRUCTIONS, MODELS,
1569
1597
  OPENAI_VOICE, RETRIEVAL,
@@ -1582,13 +1610,14 @@ export {
1582
1610
  init,
1583
1611
  initChat,
1584
1612
  jpeg,
1613
+ getAi,
1585
1614
  listFiles,
1586
1615
  listGptFineTuningEvents,
1587
1616
  listGptFineTuningJobs,
1588
1617
  listOpenAIModels,
1589
1618
  ogg,
1590
- prompt, promptAzure, promptChatGPT,
1591
- promptClaude,
1619
+ prompt, promptOpenAI,
1620
+ promptAnthropic,
1592
1621
  promptGemini,
1593
1622
  promptOllama,
1594
1623
  resetSession,
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": "1998.2.58",
4
+ "version": "1998.2.60",
5
5
  "private": false,
6
6
  "homepage": "https://github.com/Leask/utilitas",
7
7
  "main": "index.mjs",
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": "1998.2.58",
4
+ "version": "1998.2.60",
5
5
  "private": false,
6
6
  "homepage": "https://github.com/Leask/utilitas",
7
7
  "main": "index.mjs",