utilitas 1998.2.59 → 1998.2.61

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
@@ -42,32 +42,37 @@ You may be provided with some tools(functions) to help you gather information an
42
42
 
43
43
  const _NEED = [
44
44
  '@anthropic-ai/sdk', '@anthropic-ai/vertex-sdk', '@google/generative-ai',
45
- 'js-tiktoken', 'ollama', 'OpenAI',
45
+ 'js-tiktoken', 'OpenAI',
46
46
  ];
47
47
 
48
48
  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, GEMMA_3_27B,
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',
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
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,
@@ -342,24 +346,6 @@ const MODELS = {
342
346
  embedding: true,
343
347
  requestLimitsRPM: 1500,
344
348
  },
345
- [CLAUDE_35_SONNET]: { // https://docs.anthropic.com/en/docs/about-claude/models
346
- contextWindow: 200 * 1000,
347
- maxOutputTokens: 8192,
348
- imageCostTokens: size8k / 750,
349
- documentCostTokens: 3000 * 100, // 100 pages: https://docs.anthropic.com/en/docs/build-with-claude/pdf-support
350
- maxImagePerPrompt: 5, // https://docs.anthropic.com/en/docs/build-with-claude/vision
351
- maxImageSize: 1092, // by pixels
352
- maxDocumentPages: 100,
353
- maxDocumentFile: 1024 * 1024 * 32, // 32MB
354
- requestLimitsRPM: 50,
355
- tokenLimitsITPM: 40000,
356
- tokenLimitsOTPM: 8000,
357
- trainingData: 'Apr 2024',
358
- tools: true,
359
- supportedMimeTypes: [
360
- png, jpeg, gif, webp, pdf,
361
- ],
362
- },
363
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
364
350
  [CLOUD_37_SONNET]: {
365
351
  contextWindow: 200 * 1000,
@@ -382,9 +368,8 @@ const MODELS = {
382
368
  },
383
369
  };
384
370
 
385
- MODELS[CLAUDE_35_HAIKU] = MODELS[CLAUDE_35_SONNET];
386
- MODELS[DEEPSEEK_R1_32B] = MODELS[DEEPSEEK_R1];
387
371
  MODELS[DEEPSEEK_R1_70B] = MODELS[DEEPSEEK_R1];
372
+ MODELS[GEMMA327B] = MODELS[GEMMA_3_27B];
388
373
 
389
374
  for (const n in MODELS) {
390
375
  MODELS[n]['name'] = n;
@@ -514,56 +499,93 @@ const toolsGemini = async () => (await toolsOpenAI()).map(x => ({
514
499
  }
515
500
  }));
516
501
 
517
- const init = async (options) => {
502
+ const init = async (options = {}) => {
503
+ const id = newAiId();
518
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 };
519
510
  switch (provider) {
520
- case OPENAI: case AZURE:
521
- if (options?.apiKey) {
522
- provider === AZURE && assert(
523
- options?.baseURL, 'Azure api endpoint is required.'
524
- );
525
- const libOpenAI = await need('openai', { raw: true });
526
- const openai = new (options?.endpoint && options?.deployment
527
- ? libOpenAI.AzureOpenAI : libOpenAI.OpenAI)(options);
528
- clients[provider] = { client: openai, clientBeta: openai.beta };
529
- }
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
+ };
530
537
  break;
531
538
  case GEMINI:
532
- if (options?.apiKey) {
533
- const { GoogleGenerativeAI } = await need('@google/generative-ai');
534
- const genAi = new GoogleGenerativeAI(options.apiKey);
535
- clients[provider] = { client: genAi };
536
- }
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
+ };
537
546
  break;
538
- case CLAUDE:
539
- if (options?.apiKey || (options?.credentials && options?.projectId)) {
540
- // https://github.com/anthropics/anthropic-sdk-typescript/tree/main/packages/vertex-sdk
541
- const Anthropic = (await need(options?.credentials
542
- ? '@anthropic-ai/vertex-sdk' : '@anthropic-ai/sdk', { raw: true }))[
543
- options?.credentials ? 'AnthropicVertex' : 'Anthropic'
544
- ];
545
- if (options?.credentials) {
546
- process.env['GOOGLE_APPLICATION_CREDENTIALS'] = options.credentials;
547
- process.env['ANTHROPIC_VERTEX_PROJECT_ID'] = options.projectId;
548
- }
549
- const anthropic = new Anthropic({
550
- ...options?.apiKey ? { apiKey: options.apiKey } : {},
551
- ...options?.credentials ? { region: options?.region || 'us-east5' } : {},
552
- });
553
- clients[provider] = { client: anthropic };
554
- }
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
+ };
555
566
  break;
556
567
  case OLLAMA:
557
- clients[provider] || (clients[provider] = {
558
- client: new (await need('ollama', { raw: true })).Ollama(options),
559
- model: options?.model || DEFAULT_MODELS[OLLAMA],
560
- });
568
+ // https://github.com/ollama/ollama/blob/main/docs/openai.md
569
+ ais[id] = {
570
+ id, provider, model, client: await OpenAI({
571
+ baseURL: 'http://localhost:11434/v1/', apiKey: 'ollama',
572
+ ...options
573
+ }),
574
+ prompt: async (cnt, opts) => await promptOpenAI(id, cnt, opts),
575
+ };
561
576
  break;
562
577
  default:
563
- throwError(`Invalid AI provider: ${options?.provider || 'null'}`);
578
+ throwError(`Invalid AI provider: ${options.provider || 'null'}.`);
579
+ }
580
+ return ais[id];
581
+ };
582
+
583
+ const getAi = async (id, options) => {
584
+ if (id) {
585
+ if (ais[id]) { return options?.client ? ais[id]?.client : ais[id]; }
586
+ else { throwError(`AI not found: ${id}.`); }
564
587
  }
565
- assert(clients[provider], NOT_INIT);
566
- return clients[provider];
588
+ return ais;
567
589
  };
568
590
 
569
591
  const countTokens = async (input, options) => {
@@ -619,14 +641,6 @@ const buildGptMessage = (content, options) => {
619
641
  return message;
620
642
  };
621
643
 
622
- const buildOllamaMessage = (content, options) => {
623
- const message = String.isString(content) ? {
624
- role: options?.role || user, content,
625
- } : content;
626
- assertContent(message.content);
627
- return message;
628
- };
629
-
630
644
  const buildGeminiParts = (text, attachments) => {
631
645
  // Gemini API does not allow empty text, even you prompt with attachments.
632
646
  const message = [...text?.length || attachments?.length ? [{
@@ -676,8 +690,8 @@ const buildGeminiHistory = (text, options) => buildGeminiMessage(
676
690
  text, { ...options || {}, history: true }
677
691
  );
678
692
 
679
- const [getOpenAIClient, getGeminiClient, getOllamaClient, getClaudeClient]
680
- = [OPENAI, GEMINI, OLLAMA, CLAUDE].map(
693
+ const [getOpenAIClient, getGeminiClient, getClaudeClient]
694
+ = [OPENAI, GEMINI, CLAUDE].map(
681
695
  x => async options => await init({ ...provider(x), ...options })
682
696
  );
683
697
 
@@ -723,8 +737,7 @@ const packResp = async (resp, options) => {
723
737
  ])) && (audio = await convert(audio, {
724
738
  input: BUFFER, expected: BUFFER, ...options || {},
725
739
  }));
726
- options?.jsonMode && !options?.delta && !options?.processing
727
- && (json = parseJson(simpleText));
740
+ options?.jsonMode && !options?.delta && (json = parseJson(simpleText, null));
728
741
  if (options?.simple && options?.audioMode) { return audio; }
729
742
  else if (options?.simple && options?.jsonMode) { return json; }
730
743
  else if (options?.simple) { return simpleText; }
@@ -810,10 +823,6 @@ const buildPrompts = async (model, input, options = {}) => {
810
823
  systemPrompt = options.systemPrompt;
811
824
  prompt = buildClaudeMessage(content, { ...options, cache_control: true });
812
825
  break;
813
- case OLLAMA:
814
- systemPrompt = buildOllamaMessage(options.systemPrompt, _system);
815
- prompt = buildOllamaMessage(content, options);
816
- break;
817
826
  case GEMINI:
818
827
  const _role = { role: options.model === GEMMA_3_27B ? user : system };
819
828
  systemPrompt = buildGeminiHistory(options.systemPrompt, _role);
@@ -833,10 +842,6 @@ const buildPrompts = async (model, input, options = {}) => {
833
842
  history.push(buildClaudeMessage(x.request, _user));
834
843
  history.push(buildClaudeMessage(x.response, _assistant));
835
844
  break;
836
- case OLLAMA:
837
- history.push(buildOllamaMessage(x.request, _user));
838
- history.push(buildOllamaMessage(x.response, _assistant));
839
- break;
840
845
  case GEMINI:
841
846
  if (options.attachments?.length) { return; }
842
847
  history.push(buildGeminiHistory(x.request, _user));
@@ -845,9 +850,14 @@ const buildPrompts = async (model, input, options = {}) => {
845
850
  }
846
851
  });
847
852
  switch (options.flavor) {
848
- case CHATGPT: case CLAUDE: case OLLAMA:
853
+ case CHATGPT:
854
+ history = messages([
855
+ systemPrompt, ...history, prompt,
856
+ ...options.toolsResult?.length ? options.toolsResult : []
857
+ ]);
858
+ break;
859
+ case CLAUDE:
849
860
  history = messages([
850
- ...options.flavor === CLAUDE ? [] : [systemPrompt],
851
861
  ...history, prompt,
852
862
  ...options.toolsResult?.length ? options.toolsResult : []
853
863
  ]);
@@ -872,8 +882,7 @@ const buildPrompts = async (model, input, options = {}) => {
872
882
  content = trimTailing(trimTailing(content).slice(0, -1)) + '...';
873
883
  }
874
884
  }, model.maxInputTokens - options.attachments?.length * ATTACHMENT_TOKEN_COST);
875
- if ([CHATGPT, OLLAMA].includes(options.flavor)
876
- || options.model === GEMMA_3_27B) {
885
+ if ([CHATGPT].includes(options.flavor) || options.model === GEMMA_3_27B) {
877
886
  systemPrompt = null;
878
887
  }
879
888
  return { systemPrompt, history, prompt };
@@ -972,36 +981,32 @@ const mergeMsgs = (resp, calls) => [resp, ...calls.length ? [
972
981
  `⚠️ Tools recursion limit reached: ${MAX_TOOL_RECURSION}`
973
982
  ] : []].map(x => x.trim()).join('\n\n');
974
983
 
975
- const promptChatGPT = async (content, options = {}) => {
976
- if (options.model) { } else if (options.provider === AZURE) {
977
- options.model = DEFAULT_MODELS[AZURE];
978
- } else if (options.reasoning) {
979
- options.model = DEFAULT_MODELS[CHATGPT_REASONING];
980
- } else {
981
- options.model = DEFAULT_MODELS[CHATGPT];
982
- }
983
- let [_MODEL, result, resultAudio, event, resultTools, responded] = [
984
- MODELS[options.model], options?.result ?? '', Buffer.alloc(0), null, [],
985
- false
984
+ const promptOpenAI = async (aiId, content, options = {}) => {
985
+ let { provider, client, model } = await getAi(aiId);
986
+ let [result, resultAudio, event, resultTools, responded, azure] = [
987
+ options.result ?? '', Buffer.alloc(0), null, [], false,
988
+ provider === AZURE
986
989
  ];
987
- options.reasoning && !options.reasoning_effort
988
- && (options.reasoning_effort = GPT_REASONING_EFFORT);
989
- const { client } = await getOpenAIClient(options);
990
+ options.flavor = CHATGPT;
991
+ options.model = options.model || model.name;
990
992
  const { history }
991
- = await buildPrompts(_MODEL, content, { ...options, flavor: CHATGPT });
993
+ = await buildPrompts(MODELS[options.model], content, options);
994
+ model = MODELS[options.model];
995
+ model.reasoning && !azure && !options.reasoning_effort
996
+ && (options.reasoning_effort = GPT_REASONING_EFFORT);
992
997
  const modalities = options.modalities
993
998
  || (options.audioMode ? [TEXT, AUDIO] : undefined);
994
999
  [options.audioMimeType, options.suffix] = [pcm16, 'pcm.wav'];
995
1000
  const resp = await client.chat.completions.create({
1001
+ model: azure ? undefined : options.model, ...history,
1002
+ ...options.jsonMode ? { response_format: { type: JSON_OBJECT } } : {},
996
1003
  modalities, audio: options.audio || (
997
1004
  modalities?.find?.(x => x === AUDIO)
998
1005
  && { voice: DEFAULT_MODELS[OPENAI_VOICE], format: 'pcm16' }
999
- ), ...history, ..._MODEL?.tools ? {
1006
+ ), ...model.tools && !azure ? {
1000
1007
  tools: options.tools ?? (await toolsOpenAI()).map(x => x.def),
1001
- } : {}, ...options.jsonMode ? {
1002
- response_format: { type: JSON_OBJECT }
1003
- } : {}, model: options.model, stream: true,
1004
- store: true, tool_choice: 'auto',
1008
+ tool_choice: 'auto',
1009
+ } : {}, ...azure ? {} : { store: true }, stream: true,
1005
1010
  });
1006
1011
  for await (event of resp) {
1007
1012
  event = event?.choices?.[0] || {};
@@ -1016,9 +1021,11 @@ const promptChatGPT = async (content, options = {}) => {
1016
1021
  isSet(x.index, true) && (curFunc.index = x.index);
1017
1022
  x.id && (curFunc.id = x.id);
1018
1023
  x.type && (curFunc.type = x.type);
1019
- curFunc.function || (curFunc.function = { name: '', arguments: '' });
1024
+ curFunc.function
1025
+ || (curFunc.function = { name: '', arguments: '' });
1020
1026
  x?.function?.name && (curFunc.function.name += x.function.name);
1021
- x?.function?.arguments && (curFunc.function.arguments += x.function.arguments);
1027
+ x?.function?.arguments
1028
+ && (curFunc.function.arguments += x.function.arguments);
1022
1029
  }
1023
1030
  options.result && deltaText
1024
1031
  && (responded = responded || (deltaText = `\n\n${deltaText}`));
@@ -1036,62 +1043,38 @@ const promptChatGPT = async (content, options = {}) => {
1036
1043
  };
1037
1044
  const { toolsResult, toolsResponse }
1038
1045
  = await handleToolsCall(event, { ...options, result });
1039
- if (toolsResult.length && countToolCalls(toolsResponse) < MAX_TOOL_RECURSION) {
1040
- return promptChatGPT(content, { ...options, toolsResult, result: toolsResponse });
1046
+ if (toolsResult.length
1047
+ && countToolCalls(toolsResponse) < MAX_TOOL_RECURSION) {
1048
+ return promptOpenAI(aiId, content, {
1049
+ ...options, toolsResult, result: toolsResponse,
1050
+ });
1041
1051
  }
1042
1052
  event.text = mergeMsgs(toolsResponse, toolsResult);
1043
1053
  return await packResp(event, options);
1044
1054
  };
1045
1055
 
1046
- const promptAzure = async (content, options = {}) =>
1047
- await promptChatGPT(content, { ...options, provider: AZURE });
1048
-
1049
- const promptOllama = async (content, options = {}) => {
1050
- const { client, model } = await getOllamaClient(options);
1051
- // https://github.com/ollama/ollama-js
1052
- // https://github.com/jmorganca/ollama/blob/main/examples/typescript-simplechat/client.ts
1053
- options.model = options?.model || model;
1054
- let [_MODEL, chunk, result] = [MODELS[options.model], null, ''];
1055
- const { history: h }
1056
- = await buildPrompts(_MODEL, content, { ...options, flavor: OLLAMA });
1057
- const resp = await client.chat({ model: options.model, stream: true, ...h });
1058
- for await (chunk of resp) {
1059
- const delta = chunk.message.content || '';
1060
- result += delta;
1061
- delta && await streamResp({
1062
- text: options.delta ? delta : result,
1063
- }, options);
1064
- }
1065
- return await packResp({ text: result }, options);
1066
- };
1067
-
1068
- const promptClaude = async (content, options = {}) => {
1069
- options.model = options.model || DEFAULT_MODELS[CLAUDE];
1056
+ const promptAnthropic = async (aiId, content, options = {}) => {
1057
+ const { client, model } = await getAi(aiId);
1070
1058
  let [
1071
- _MODEL, event, text, thinking, signature, result, thinkEnd, tool_use,
1072
- responded, redacted_thinking,
1073
- ] = [
1074
- MODELS[options.model], null, '', '', '', options.result ?? '', '',
1075
- [], false, [],
1076
- ];
1077
- // https://docs.anthropic.com/en/docs/build-with-claude/extended-thinking
1078
- options?.test_redacted_thinking && !options?.result && (
1059
+ event, text, thinking, signature, result, thinkEnd, tool_use,
1060
+ responded, redacted_thinking
1061
+ ] = [null, '', '', '', options.result ?? '', '', [], false, []];
1062
+ options.model = options.model || model.name;
1063
+ options.test_redacted_thinking && !result && (
1079
1064
  content += '\n\nANTHROPIC_MAGIC_STRING_TRIGGER_REDACTED_THINKING_'
1080
1065
  + '46C9A13E193C177646C7398A98432ECCCE4C1253D5E2D82641AC0E52CC2876CB'
1081
- );
1082
- const { client } = await getClaudeClient(options);
1066
+ ); // https://docs.anthropic.com/en/docs/build-with-claude/extended-thinking
1083
1067
  const { systemPrompt: system, history }
1084
- = await buildPrompts(_MODEL, content, { ...options, flavor: CLAUDE });
1068
+ = await buildPrompts(model, content, { ...options, flavor: CLAUDE });
1085
1069
  const resp = await client.beta.messages.create({
1086
- model: options.model,
1087
- max_tokens: options?.extendedThinking ? 128000 : _MODEL.maxOutputTokens,
1088
- ...history, system, stream: true,
1089
- ...options.reasoning ?? _MODEL?.reasoning ? {
1070
+ model: options.model, ...history, system, stream: true,
1071
+ max_tokens: options.extendedThinking ? 128000 : model.maxOutputTokens,
1072
+ ...(options.reasoning ?? model.reasoning) ? {
1090
1073
  thinking: options.thinking || {
1091
1074
  type: 'enabled',
1092
1075
  budget_tokens: options?.extendedThinking ? 16000 : 1024,
1093
1076
  },
1094
- } : {}, ..._MODEL?.tools ? {
1077
+ } : {}, ...model?.tools ? {
1095
1078
  tools: options.tools ?? (await toolsClaude()).map(x => x.def),
1096
1079
  tool_choice: { type: 'auto' }, betas: [
1097
1080
  // https://docs.anthropic.com/en/docs/build-with-claude/tool-use/token-efficient-tool-use
@@ -1136,7 +1119,7 @@ const promptClaude = async (content, options = {}) => {
1136
1119
  event, { ...options, result, flavor: CLAUDE },
1137
1120
  );
1138
1121
  if (tool_use.length && countToolCalls(toolsResponse) < MAX_TOOL_RECURSION) {
1139
- return await promptClaude(content, {
1122
+ return await promptAnthropic(aiId, content, {
1140
1123
  ...options, toolsResult: [...options.toolsResult || [],
1141
1124
  ...toolsResult], result: toolsResponse,
1142
1125
  });
@@ -1193,31 +1176,29 @@ const packGeminiReferences = (chunks, supports) => {
1193
1176
  return references;
1194
1177
  };
1195
1178
 
1196
- const promptGemini = async (content, options = {}) => {
1197
- options.model || (options.model = DEFAULT_MODELS[GEMINI]);
1198
- let [result, references, functionCalls, responded, _MODEL]
1199
- = [options.result ?? '', null, null, false, MODELS[options.model]];
1200
- const { client: _client } = await getGeminiClient(options);
1179
+ const promptGemini = async (aiId, content, options = {}) => {
1180
+ const { client, model } = await getAi(aiId);
1181
+ let [result, references, functionCalls, responded]
1182
+ = [options.result ?? '', null, null, false];
1183
+ options.model = options.model || model.name;
1201
1184
  const { systemPrompt: systemInstruction, history, prompt }
1202
- = await buildPrompts(_MODEL, content, { ...options, flavor: GEMINI });
1203
- const client = _client.getGenerativeModel({
1185
+ = await buildPrompts(model, content, { ...options, flavor: GEMINI });
1186
+ const _client = client.getGenerativeModel({
1204
1187
  model: options.model, systemInstruction,
1205
- ...MODELS[options.model]?.tools && !options.jsonMode ? (
1206
- options.tools ?? {
1207
- tools: [
1208
- // @todo: Gemini will failed when using these tools together.
1209
- // https://ai.google.dev/gemini-api/docs/function-calling
1210
- // { codeExecution: {} },
1211
- // { googleSearch: {} },
1212
- { functionDeclarations: (await toolsGemini()).map(x => x.def) },
1213
- ],
1214
- toolConfig: { functionCallingConfig: { mode: 'AUTO' } },
1215
- }
1216
- ) : {},
1188
+ ...model?.tools && !options.jsonMode ? (options.tools ?? {
1189
+ tools: [
1190
+ // @todo: Gemini will failed when using these tools together.
1191
+ // https://ai.google.dev/gemini-api/docs/function-calling
1192
+ // { codeExecution: {} },
1193
+ // { googleSearch: {} },
1194
+ { functionDeclarations: (await toolsGemini()).map(x => x.def) },
1195
+ ],
1196
+ toolConfig: { functionCallingConfig: { mode: 'AUTO' } },
1197
+ }) : {},
1217
1198
  });
1218
1199
  // https://github.com/google/generative-ai-js/blob/main/samples/node/advanced-chat.js
1219
1200
  // Google's bug: history is not allowed while using inline_data?
1220
- const chat = client.startChat({ history, ...generationConfig(options) });
1201
+ const chat = _client.startChat({ history, ...generationConfig(options) });
1221
1202
  const resp = await chat.sendMessageStream(prompt);
1222
1203
  for await (const chunk of resp.stream) {
1223
1204
  functionCalls || (functionCalls = chunk.functionCalls);
@@ -1242,10 +1223,11 @@ const promptGemini = async (content, options = {}) => {
1242
1223
  { role: MODEL, parts: functionCalls },
1243
1224
  { ...options, result, flavor: GEMINI }
1244
1225
  );
1245
- if (toolsResult.length && countToolCalls(toolsResponse) < MAX_TOOL_RECURSION) {
1246
- return promptGemini(content, {
1247
- ...options || {}, toolsResult: [...options?.toolsResult || [],
1248
- ...toolsResult], result: toolsResponse,
1226
+ if (toolsResult.length
1227
+ && countToolCalls(toolsResponse) < MAX_TOOL_RECURSION) {
1228
+ return promptGemini(aiId, content, {
1229
+ ...options || {}, result: toolsResponse,
1230
+ toolsResult: [...options?.toolsResult || [], ...toolsResult],
1249
1231
  });
1250
1232
  }
1251
1233
  return await packResp({
@@ -1442,10 +1424,10 @@ const talk = async (input, options) => {
1442
1424
  ...options,
1443
1425
  };
1444
1426
  switch (engine) {
1445
- case CHATGPT: resp = await promptChatGPT(input, pmtOptions); break;
1427
+ case CHATGPT: resp = await promptOpenAI(input, pmtOptions); break;
1446
1428
  case GEMINI: resp = await promptGemini(input, pmtOptions); break;
1447
- case CLAUDE: resp = await promptClaude(input, pmtOptions); break;
1448
- case OLLAMA: resp = await promptOllama(input, pmtOptions); break;
1429
+ case CLAUDE: resp = await promptAnthropic(input, pmtOptions); break;
1430
+ // case OLLAMA: resp = await promptOllama(input, pmtOptions); break;
1449
1431
  case AZURE: resp = await promptAzure(input, pmtOptions); break;
1450
1432
  default: throwError(`Invalid AI engine: '${engine}'.`);
1451
1433
  }
@@ -1568,17 +1550,17 @@ const analyzeSessions = async (sessionIds, options) => {
1568
1550
  };
1569
1551
 
1570
1552
  const PREFERRED_ENGINES = [
1571
- { client: OPENAI, func: promptChatGPT, multimodal: 0 },
1553
+ { client: OPENAI, func: promptOpenAI, multimodal: 0 },
1572
1554
  { client: GEMINI, func: promptGemini, multimodal: 1 },
1573
- { client: CLAUDE, func: promptClaude, multimodal: 2 },
1574
- { client: AZURE, func: promptAzure, multimodal: 3 },
1575
- { client: OLLAMA, func: promptOllama, multimodal: 99 },
1555
+ { client: CLAUDE, func: promptAnthropic, multimodal: 2 },
1556
+ // { client: AZURE, func: promptAzure, multimodal: 3 },
1557
+ // { client: OLLAMA, func: promptOllama, multimodal: 99 },
1576
1558
  ]; // keep gpt first to avoid gemini grounding by default
1577
1559
 
1578
1560
  export default init;
1579
1561
  export {
1580
1562
  ATTACHMENT_TOKEN_COST, CLOUD_37_SONNET, CODE_INTERPRETER, DEEPSEEK_R1,
1581
- DEEPSEEK_R1_32B, DEEPSEEK_R1_70B, DEFAULT_MODELS,
1563
+ DEEPSEEK_R1_70B, DEFAULT_MODELS,
1582
1564
  EMBEDDING_001,
1583
1565
  FUNCTION, GEMINI_20_FLASH, GEMINI_20_FLASH_THINKING, GPT_45, GPT_4O, GPT_4O_MINI, GPT_O1, GPT_O3_MINI, INSTRUCTIONS, MODELS,
1584
1566
  OPENAI_VOICE, RETRIEVAL,
@@ -1597,15 +1579,15 @@ export {
1597
1579
  init,
1598
1580
  initChat,
1599
1581
  jpeg,
1582
+ getAi,
1600
1583
  listFiles,
1601
1584
  listGptFineTuningEvents,
1602
1585
  listGptFineTuningJobs,
1603
1586
  listOpenAIModels,
1604
1587
  ogg,
1605
- prompt, promptAzure, promptChatGPT,
1606
- promptClaude,
1588
+ prompt, promptOpenAI,
1589
+ promptAnthropic,
1607
1590
  promptGemini,
1608
- promptOllama,
1609
1591
  resetSession,
1610
1592
  tailGptFineTuningEvents,
1611
1593
  talk,