utilitas 1998.2.38 → 1998.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/lib/alan.mjs CHANGED
@@ -498,26 +498,7 @@ const init = async (options) => {
498
498
  if (options?.apiKey) {
499
499
  const { GoogleGenerativeAI } = await need('@google/generative-ai');
500
500
  const genAi = new GoogleGenerativeAI(options.apiKey);
501
- const genModel = options?.model || DEFAULT_MODELS[GEMINI];
502
- clients[provider] = {
503
- generative: genAi.getGenerativeModel({
504
- model: genModel,
505
- systemInstruction: { role: system, parts: [{ text: INSTRUCTIONS }] },
506
- ...MODELS[genModel]?.tools ? (options?.tools ?? {
507
- tools: [
508
- // @todo: Gemini will failed when using these tools together.
509
- // https://ai.google.dev/gemini-api/docs/function-calling
510
- // { codeExecution: {} },
511
- // { googleSearch: {} },
512
- { functionDeclarations: toolsGemini.map(x => x.def) },
513
- ],
514
- toolConfig: { functionCallingConfig: { mode: 'AUTO' } },
515
- }) : {},
516
- }),
517
- embedding: genAi.getGenerativeModel({
518
- model: DEFAULT_MODELS[GEMINI_EMEDDING],
519
- }), genModel,
520
- };
501
+ clients[provider] = { client: genAi };
521
502
  }
522
503
  break;
523
504
  case CLAUDE:
@@ -623,7 +604,7 @@ const buildGeminiParts = (text, attachments) => {
623
604
 
624
605
  const buildGeminiMessage = (content, options) => {
625
606
  content = content || '';
626
- const attachments = (options?.attachments || []).map(x => ({
607
+ const attachments = (options?.attachments?.length ? options.attachments : []).map(x => ({
627
608
  inlineData: { mimeType: x.mime_type, data: x.data }
628
609
  }));
629
610
  return String.isString(content) ? (options?.history ? {
@@ -634,7 +615,7 @@ const buildGeminiMessage = (content, options) => {
634
615
 
635
616
  const buildClaudeMessage = (text, options) => {
636
617
  assert(text, 'Text is required.');
637
- const attachments = (options?.attachments || []).map(x => {
618
+ const attachments = (options?.attachments?.length ? options?.attachments : []).map(x => {
638
619
  let type = '';
639
620
  if ([pdf].includes(x.mime_type)) {
640
621
  type = 'document';
@@ -669,9 +650,38 @@ const listOpenAIModels = async (options) => {
669
650
  return options?.raw ? resp : resp.data;
670
651
  };
671
652
 
653
+ const streamResp = async (resp, options) => {
654
+ const msg = await packResp(resp, { ...options, processing: true });
655
+ return options?.stream && (msg?.text || msg?.audio?.length)
656
+ && await ignoreErrFunc(async () => await options.stream(msg), LOG);
657
+ };
658
+
659
+ const getInfoEnd = text => Math.max(...[THINK_END, TOOLS_END].map(x => {
660
+ const keyEnd = text.indexOf(`${x}\n`);
661
+ return keyEnd >= 0 ? (keyEnd + x.length) : 0;
662
+ }));
663
+
672
664
  const packResp = async (resp, options) => {
673
- let { text: txt, audio, references }
674
- = String.isString(resp) ? { text: resp } : resp;
665
+ if (options?.raw) { return resp; }
666
+ let [
667
+ txt, audio, references, markdown, simpleText, referencesMarkdown, end,
668
+ json
669
+ ] = [
670
+ resp.text // ChatGPT / Claude / Gemini
671
+ || resp?.message?.content || '', // Ollama @tudo: Need to be updated
672
+ resp?.audio?.data, // ChatGPT audio mode
673
+ resp?.references, // Gemini references
674
+ '', '', '', null,
675
+ ];
676
+ markdown = simpleText = txt;
677
+ while ((end = getInfoEnd(simpleText))) {
678
+ simpleText = simpleText.slice(end).trim();
679
+ end = getInfoEnd(simpleText);
680
+ }
681
+ [THINK_STR, TOOLS_STR].map(x => {
682
+ const str = simpleText.indexOf(x);
683
+ str >= 0 && (simpleText = simpleText.slice(0, str).trim());
684
+ });
675
685
  audio && (audio = Buffer.isBuffer(audio) ? audio : await convert(audio, {
676
686
  input: BASE64, expected: BUFFER,
677
687
  })) && audio.length && (audio = Buffer.concat([
@@ -679,6 +689,14 @@ const packResp = async (resp, options) => {
679
689
  ])) && (audio = await convert(audio, {
680
690
  input: BUFFER, expected: BUFFER, ...options || {},
681
691
  }));
692
+ options?.jsonMode && !options?.delta && !options?.processing
693
+ && (json = parseJson(simpleText));
694
+ if (options?.simple && options?.audioMode) { return audio; }
695
+ else if (options?.simple && options?.jsonMode) { return json; }
696
+ else if (options?.simple) { return simpleText; }
697
+ else if (options?.jsonMode) {
698
+ markdown = `\`\`\`json\n${simpleText}\n\`\`\``;
699
+ }
682
700
  // references debug codes:
683
701
  // references = {
684
702
  // "segments": [
@@ -701,77 +719,127 @@ const packResp = async (resp, options) => {
701
719
  // },
702
720
  // ]
703
721
  // };
704
- let [richText, referencesMarkdown] = [null, null];
705
- if (!options?.jsonMode) {
706
- if (!options?.processing
707
- && references?.segments?.length && references?.links?.length) {
708
- richText = txt;
709
- for (let i = references.segments.length - 1; i >= 0; i--) {
710
- let idx = richText.indexOf(references.segments[i].text);
711
- if (idx < 0) { continue; }
712
- idx += references.segments[i].text.length;
713
- richText = richText.slice(0, idx)
714
- + references.segments[i].indices.map(y => ` (${y + 1})`).join('')
715
- + richText.slice(idx);
716
- }
717
- referencesMarkdown = 'References:\n\n' + references.links.map((x, i) => {
718
- return `${i + 1}. [${x.title}](${x.uri})`;
719
- }).join('\n');
722
+ if (references?.segments?.length && references?.links?.length) {
723
+ for (let i = references.segments.length - 1; i >= 0; i--) {
724
+ let idx = markdown.indexOf(references.segments[i].text);
725
+ if (idx < 0) { continue; }
726
+ idx += references.segments[i].text.length;
727
+ markdown = markdown.slice(0, idx)
728
+ + references.segments[i].indices.map(y => ` (${y + 1})`).join('')
729
+ + markdown.slice(idx);
720
730
  }
721
- let lines = (richText || txt).split('\n');
722
- for (let i in lines) {
723
- switch (lines[i]) {
724
- case THINK_STR:
725
- lines[i] = MD_CODE + THINK;
726
- break;
727
- case TOOLS_STR:
728
- lines[i] = MD_CODE + TOOLS;
729
- break;
730
- case THINK_END:
731
- case TOOLS_END:
732
- lines[i] = MD_CODE;
733
- }
731
+ referencesMarkdown = 'References:\n\n' + references.links.map(
732
+ (x, i) => `${i + 1}. [${x.title}](${x.uri})`
733
+ ).join('\n');
734
+ }
735
+ markdown = markdown.split('\n');
736
+ for (let i in markdown) {
737
+ switch (markdown[i]) {
738
+ case THINK_STR: markdown[i] = MD_CODE + THINK; break;
739
+ case TOOLS_STR: markdown[i] = MD_CODE + TOOLS; break;
740
+ case THINK_END: case TOOLS_END: markdown[i] = MD_CODE;
734
741
  }
735
- richText = lines.join('\n').trim();
742
+ }
743
+ markdown = markdown.join('\n');
744
+ if (!options?.delta && !options?.processing) {
745
+ txt = txt.trim();
746
+ markdown = markdown.trim();
736
747
  }
737
748
  return {
738
- ...text(txt), ...options?.jsonMode && !(
739
- options?.delta && options?.processing
740
- ) ? { json: parseJson(txt) } : {},
741
- ...richText ? { richText } : {},
742
- ...references ? { references } : {},
749
+ ...text(txt), ...options?.jsonMode ? { json } : {},
750
+ markdown, ...references ? { references } : {},
743
751
  ...referencesMarkdown ? { referencesMarkdown } : {},
744
752
  ...audio ? { audio, audioMimeType: options?.audioMimeType } : {},
753
+ processing: options?.processing,
745
754
  model: options?.model,
746
755
  };
747
756
  };
748
757
 
749
- const streamResp = async (resp, options) => {
750
- const msg = await packGptResp(resp, { ...options, processing: true });
751
- return options?.stream && (msg?.text || msg?.audio?.length)
752
- && await ignoreErrFunc(async () => await options.stream(msg), LOG);
753
- };
754
-
755
- const packGptResp = async (resp, options) => {
756
- // simple mode is not recommended for streaming responses
757
- let text = resp.text // ChatGPT / Claude / Gemini
758
- || resp?.message?.content || ''; // Ollama
759
- const audio = resp?.message?.audio?.data; // ChatGPT audio mode
760
- if (options?.raw) { return resp; }
761
- else if (options?.simple && options?.jsonMode) { return parseJson(text); }
762
- else if (options?.simple && options?.audioMode) { return audio; }
763
- else if (options?.simple) {
764
- for (const key of [[THINK_STR, THINK_END], [TOOLS_STR, TOOLS_END]]) {
765
- const [findStr, findEnd] = key.map(x => text.indexOf(x));
766
- if (findStr >= 0 && findEnd >= 0 && findStr < findEnd) {
767
- text = text.split('')
768
- text.splice(findStr, findEnd + THINK_END.length)
769
- text = text.join('').trim();
758
+ const buildPrompts = async (model, input, options = {}) => {
759
+ assert(!(
760
+ options.jsonMode && !model?.json
761
+ ), `This model does not support JSON output: ${options.model}`);
762
+ assert(!(
763
+ options.reasoning && !model?.reasoning
764
+ ), `This model does not support reasoning: ${options.model}`);
765
+ let [systemPrompt, history, content, prompt, _system, _user, _assistant] = [
766
+ null, null, input || ATTACHMENTS, null, [], null, // length hack: ATTACHMENTS
767
+ { role: system }, { role: user }, { role: assistant }
768
+ ];
769
+ options.systemPrompt = options.systemPrompt || INSTRUCTIONS;
770
+ options.attachments = (
771
+ options.attachments?.length ? options.attachments : []
772
+ ).filter(x => [
773
+ ...model?.supportedMimeTypes || [], ...model.supportedAudioTypes || []
774
+ ].includes(x.mime_type));
775
+ switch (options.flavor) {
776
+ case CHATGPT:
777
+ systemPrompt = buildGptMessage(options.systemPrompt, _system);
778
+ prompt = buildGptMessage(content, options);
779
+ break;
780
+ case CLAUDE:
781
+ systemPrompt = buildClaudeMessage(options.systemPrompt, _system);
782
+ prompt = buildClaudeMessage(content, options)
783
+ break;
784
+ case OLLAMA:
785
+ systemPrompt = buildOllamaMessage(options.systemPrompt, _system);
786
+ prompt = buildOllamaMessage(content, options);
787
+ break;
788
+ case GEMINI:
789
+ systemPrompt = buildGeminiHistory(options.systemPrompt, _system);
790
+ prompt = options.toolsResult?.[options.toolsResult?.length - 1]?.parts
791
+ || buildGeminiMessage(content, options)
792
+ break;
793
+ }
794
+ const msgBuilder = () => {
795
+ history = [];
796
+ (options.messages?.length ? options.messages : []).map(x => {
797
+ switch (options.flavor) {
798
+ case CHATGPT:
799
+ history.push(buildGptMessage(x.request, _user));
800
+ history.push(buildGptMessage(x.response, _assistant));
801
+ break;
802
+ case CLAUDE:
803
+ history.push(buildClaudeMessage(x.request, _user));
804
+ history.push(buildClaudeMessage(x.response, _assistant));
805
+ break;
806
+ case OLLAMA:
807
+ history.push(buildClaudeMessage(x.request, _user));
808
+ history.push(buildClaudeMessage(x.response, _assistant));
809
+ break;
810
+ case GEMINI:
811
+ if (options.attachments?.length) { return; }
812
+ history.push(buildGeminiHistory(x.request, _user));
813
+ history.push(buildGeminiHistory(x.response, { role: MODEL }));
814
+ break;
770
815
  }
816
+ });
817
+ switch (options.flavor) {
818
+ case CHATGPT: case CLAUDE: case OLLAMA:
819
+ history.push(prompt, ...options.toolsResult?.length
820
+ ? options.toolsResult : []);
821
+ history = messages(history);
822
+ break;
823
+ case GEMINI:
824
+ history.push(
825
+ ...options.toolsResult?.length ? [
826
+ buildGeminiHistory(content, { ...options, role: user }),
827
+ ...options.toolsResult.slice(0, options.toolsResult.length - 1)
828
+ ] : []
829
+ );
830
+ break;
771
831
  }
772
- return text;
773
- }
774
- return await packResp({ text, audio, references: resp?.references }, options);
832
+ };
833
+ msgBuilder();
834
+ await trimPrompt(() => [systemPrompt, history, prompt], () => {
835
+ if (options.messages.length) {
836
+ options.messages.shift();
837
+ msgBuilder();
838
+ } else {
839
+ content = trimTailing(trimTailing(content).slice(0, -1)) + '...';
840
+ }
841
+ }, model.maxInputTokens - options.attachments?.length * ATTACHMENT_TOKEN_COST);
842
+ return { systemPrompt, history, prompt };
775
843
  };
776
844
 
777
845
  const handleToolsCall = async (msg, options) => {
@@ -867,43 +935,32 @@ const mergeMsgs = (resp, calls) => [resp, ...calls.length ? [
867
935
  ] : []].map(x => x.trim()).join('\n\n');
868
936
 
869
937
  const promptChatGPT = async (content, options = {}) => {
870
- const { client } = await getOpenAIClient(options);
871
- // https://github.com/openai/openai-node?tab=readme-ov-file#streaming-responses
872
- // custom api endpoint not supported vision apis @todo by @Leask
873
- // Structured Outputs: https://openai.com/index/introducing-structured-outputs-in-the-api/
874
- client.baseURL !== OPENAI_BASE_URL
875
- && options?.attachments?.length && (options.attachments = []);
876
- if (options?.model) { } else if (options?.provider === AZURE) {
938
+ if (options.model) { } else if (options.provider === AZURE) {
877
939
  options.model = DEFAULT_MODELS[AZURE];
878
- } else if (options?.reasoning) {
940
+ } else if (options.reasoning) {
879
941
  options.model = DEFAULT_MODELS[CHATGPT_REASONING];
880
942
  } else {
881
943
  options.model = DEFAULT_MODELS[CHATGPT];
882
944
  }
883
- options?.reasoning && !options?.reasoning_effort
945
+ let [_MODEL, result, resultAudio, event, resultTools, responded] = [
946
+ MODELS[options.model], options?.result ?? '', Buffer.alloc(0), null, [],
947
+ false
948
+ ];
949
+ options.reasoning && !options.reasoning_effort
884
950
  && (options.reasoning_effort = GPT_REASONING_EFFORT);
885
- const message = buildGptMessage(content, options);
886
- const modalities = options?.modalities || (
887
- options?.audioMode ? [TEXT, AUDIO] : undefined
888
- );
889
- assert(!(
890
- options?.jsonMode && !MODELS[options.model]?.json
891
- ), `This model does not support JSON output: ${options.model}`);
892
- assert(!(
893
- options?.reasoning && !MODELS[options.model]?.reasoning
894
- ), `This model does not support reasoning: ${options.model}`);
951
+ const { client } = await getOpenAIClient(options);
952
+ const { history }
953
+ = await buildPrompts(_MODEL, content, { ...options, flavor: CHATGPT });
954
+ const modalities = options.modalities
955
+ || (options.audioMode ? [TEXT, AUDIO] : undefined);
895
956
  [options.audioMimeType, options.suffix] = [pcm16, 'pcm.wav'];
896
- let [result, resultAudio, event, resultTools, responded]
897
- = [options?.result ?? '', Buffer.alloc(0), null, [], false];
898
957
  const resp = await client.chat.completions.create({
899
- modalities, audio: options?.audio || (
958
+ modalities, audio: options.audio || (
900
959
  modalities?.find?.(x => x === AUDIO)
901
960
  && { voice: DEFAULT_MODELS[OPENAI_VOICE], format: 'pcm16' }
902
- ), ...messages([
903
- ...options?.messages || [], message, ...options?.toolsResult || [],
904
- ]), ...MODELS[options.model]?.tools ? {
905
- tools: options?.tools ?? tools.map(x => x.def),
906
- } : {}, ...options?.jsonMode ? {
961
+ ), ...history, ..._MODEL?.tools ? {
962
+ tools: options.tools ?? tools.map(x => x.def),
963
+ } : {}, ...options.jsonMode ? {
907
964
  response_format: { type: JSON_OBJECT }
908
965
  } : {}, model: options.model, stream: true,
909
966
  store: true, tool_choice: 'auto',
@@ -925,12 +982,13 @@ const promptChatGPT = async (content, options = {}) => {
925
982
  x?.function?.name && (curFunc.function.name += x.function.name);
926
983
  x?.function?.arguments && (curFunc.function.arguments += x.function.arguments);
927
984
  }
928
- deltaText && (responded = responded || (deltaText = `\n\n${deltaText}`));
985
+ options.result && deltaText
986
+ && (responded = responded || (deltaText = `\n\n${deltaText}`));
929
987
  result += deltaText;
930
988
  resultAudio = Buffer.concat([resultAudio, deltaAudio]);
931
- const respAudio = options?.delta ? deltaAudio : resultAudio;
932
- await streamResp({
933
- text: options?.delta ? deltaText : result,
989
+ const respAudio = options.delta ? deltaAudio : resultAudio;
990
+ (deltaText || deltaAudio?.length) && await streamResp({
991
+ text: options.delta ? deltaText : result,
934
992
  ...respAudio.length ? { audio: { data: respAudio } } : {},
935
993
  }, options);
936
994
  }
@@ -944,7 +1002,7 @@ const promptChatGPT = async (content, options = {}) => {
944
1002
  return promptChatGPT(content, { ...options, toolsResult, result: toolsResponse });
945
1003
  }
946
1004
  event.text = mergeMsgs(toolsResponse, toolsResult);
947
- return await packGptResp(event, options);
1005
+ return await packResp(event, options);
948
1006
  };
949
1007
 
950
1008
  const promptAzure = async (content, options = {}) =>
@@ -955,61 +1013,56 @@ const promptOllama = async (content, options = {}) => {
955
1013
  // https://github.com/ollama/ollama-js
956
1014
  // https://github.com/jmorganca/ollama/blob/main/examples/typescript-simplechat/client.ts
957
1015
  options.model = options?.model || model;
958
- const resp = await client.chat({
959
- model: options.model, stream: true,
960
- ...messages([...options?.messages || [], buildOllamaMessage(content)]),
961
- })
962
- let [chunk, result] = [null, ''];
1016
+ let [_MODEL, chunk, result] = [MODELS[options.model], null, ''];
1017
+ const { history: h }
1018
+ = await buildPrompts(_MODEL, content, { ...options, flavor: OLLAMA });
1019
+ const resp = await client.chat({ model: options.model, stream: true, ...h });
963
1020
  for await (chunk of resp) {
964
1021
  const delta = chunk.message.content || '';
965
- if (delta === '') { continue; }
966
1022
  result += delta;
967
- chunk.message.content = options?.delta ? delta : result;
968
- await ignoreErrFunc(async () => await options?.stream?.(
969
- await packGptResp(chunk, { ...options || {}, processing: true })
970
- ), LOG);
1023
+ delta && await streamResp({
1024
+ text: options.delta ? delta : result,
1025
+ }, options);
971
1026
  }
972
- chunk.message.content = result;
973
- return await packGptResp(chunk, options);
1027
+ return await packResp({ text: result }, options);
974
1028
  };
975
1029
 
976
1030
  const promptClaude = async (content, options = {}) => {
1031
+ options.model = options.model || DEFAULT_MODELS[CLAUDE];
1032
+ let [_MODEL, event, text, thinking, signature, result, thinkEnd, tool_use]
1033
+ = [MODELS[options.model], null, '', '', '', options.result ?? '', '', []];
977
1034
  const { client } = await getClaudeClient(options);
978
- options.model = options?.model || DEFAULT_MODELS[CLAUDE];
1035
+ const { history }
1036
+ = await buildPrompts(_MODEL, content, { ...options, flavor: CLAUDE });
979
1037
  const resp = await client.messages.create({
980
- model: options.model, max_tokens: MODELS[options.model].maxOutputTokens,
981
- messages: [
982
- ...options?.messages || [], buildClaudeMessage(content, options),
983
- ...options?.toolsResult || [],
984
- ], stream: true,
985
- ...options?.reasoning ?? MODELS[options.model]?.reasoning ? {
986
- thinking: options?.thinking || { type: 'enabled', budget_tokens: 1024 },
987
- } : {}, // https://docs.anthropic.com/en/docs/build-with-claude/extended-thinking
988
- ...MODELS[options.model]?.tools ? {
989
- tools: options?.tools ?? toolsClaude.map(x => x.def),
1038
+ model: options.model, max_tokens: _MODEL.maxOutputTokens, ...history,
1039
+ stream: true, ...options.reasoning ?? _MODEL?.reasoning ? {
1040
+ thinking: options.thinking || { type: 'enabled', budget_tokens: 1024 },
1041
+ } : {}, ..._MODEL?.tools ? { // https://docs.anthropic.com/en/docs/build-with-claude/extended-thinking
1042
+ tools: options.tools ?? toolsClaude.map(x => x.def),
990
1043
  tool_choice: { type: 'auto' },
991
1044
  } : {},
992
1045
  });
993
- let [event, text, thinking, signature, result, thinkEnd, tool_use]
994
- = [null, '', '', '', options?.result ?? '', '', []];
995
1046
  for await (const chunk of resp) {
996
1047
  event = chunk?.content_block || chunk?.delta || {};
997
- let [thkDelta, txtDelta] = [event.thinking || '', event.text || ''];
998
- text += txtDelta;
999
- thinking += thkDelta;
1048
+ let [deltaThink, deltaText] = [event.thinking || '', event.text || ''];
1049
+ text += deltaText;
1050
+ thinking += deltaThink;
1000
1051
  signature = signature || event?.signature || '';
1001
- thkDelta && thkDelta === thinking
1002
- && (thkDelta = `${THINK_STR}\n${thkDelta}`);
1003
- thinking && txtDelta && !thinkEnd
1004
- && (thinkEnd = thkDelta = `${thkDelta}\n${THINK_END}\n\n`);
1052
+ deltaThink && deltaThink === thinking
1053
+ && (deltaThink = `${THINK_STR}\n${deltaThink}`);
1054
+ thinking && deltaText && !thinkEnd
1055
+ && (thinkEnd = deltaThink = `${deltaThink}\n${THINK_END}\n\n`);
1005
1056
  if (event?.type === 'tool_use') {
1006
1057
  tool_use.push({ ...event, input: '' });
1007
1058
  } else if (event.partial_json) {
1008
1059
  tool_use[tool_use.length - 1].input += event.partial_json;
1009
1060
  }
1010
- txtDelta = thkDelta + txtDelta;
1011
- result += txtDelta;
1012
- await streamResp({ text: options?.delta ? txtDelta : result }, options);
1061
+ deltaText = deltaThink + deltaText;
1062
+ result += deltaText;
1063
+ deltaText && await streamResp({
1064
+ text: options.delta ? deltaText : result,
1065
+ }, options);
1013
1066
  }
1014
1067
  event = {
1015
1068
  role: assistant, content: [
@@ -1022,11 +1075,11 @@ const promptClaude = async (content, options = {}) => {
1022
1075
  );
1023
1076
  if (tool_use.length && countToolCalls(toolsResponse) < MAX_TOOL_RECURSION) {
1024
1077
  return await promptClaude(content, {
1025
- ...options, toolsResult: [...options?.toolsResult || [],
1078
+ ...options, toolsResult: [...options.toolsResult || [],
1026
1079
  ...toolsResult], result: toolsResponse,
1027
1080
  });
1028
1081
  }
1029
- return packGptResp({ text: mergeMsgs(toolsResponse, tool_use) }, options);
1082
+ return packResp({ text: mergeMsgs(toolsResponse, tool_use) }, options);
1030
1083
  };
1031
1084
 
1032
1085
  const uploadFile = async (input, options) => {
@@ -1079,29 +1132,32 @@ const packGeminiReferences = (chunks, supports) => {
1079
1132
  };
1080
1133
 
1081
1134
  const promptGemini = async (content, options = {}) => {
1082
- const { generative, genModel } = await getGeminiClient(options);
1135
+ options.model || (options.model = DEFAULT_MODELS[GEMINI]);
1136
+ let [result, references, functionCalls, responded, _MODEL]
1137
+ = [options.result ?? '', null, null, false, MODELS[options.model]];
1138
+ const { client: _client } = await getGeminiClient(options);
1139
+ const { systemPrompt: systemInstruction, history, prompt }
1140
+ = await buildPrompts(_MODEL, content, { ...options, flavor: GEMINI });
1141
+ const client = _client.getGenerativeModel({
1142
+ model: options.model, systemInstruction,
1143
+ ...MODELS[options.model]?.tools && !options.jsonMode ? (
1144
+ options.tools ?? {
1145
+ tools: [
1146
+ // @todo: Gemini will failed when using these tools together.
1147
+ // https://ai.google.dev/gemini-api/docs/function-calling
1148
+ // { codeExecution: {} },
1149
+ // { googleSearch: {} },
1150
+ { functionDeclarations: toolsGemini.map(x => x.def) },
1151
+ ],
1152
+ toolConfig: { functionCallingConfig: { mode: 'AUTO' } },
1153
+ }
1154
+ ) : {},
1155
+ });
1083
1156
  // https://github.com/google/generative-ai-js/blob/main/samples/node/advanced-chat.js
1084
1157
  // @todo: check this issue similar to Vertex AI:
1085
1158
  // Google's bug: history is not allowed while using inline_data?
1086
- assert(!(
1087
- options?.jsonMode && MODELS[genModel]?.json == false
1088
- ), `This model does not support JSON output: ${genModel} `);
1089
- options.model = genModel;
1090
- const chat = generative.startChat({
1091
- history: [
1092
- ...options?.messages && !options?.attachments?.length ? options.messages : [],
1093
- ...options?.toolsResult ? [
1094
- buildGeminiMessage(content, { ...options, history: true }),
1095
- ...options.toolsResult.slice(0, options.toolsResult.length - 1)
1096
- ] : []
1097
- ], ...generationConfig(options),
1098
- });
1099
- const resp = await chat.sendMessageStream(
1100
- options?.toolsResult?.[options?.toolsResult?.length]?.parts
1101
- || buildGeminiMessage(content, options)
1102
- );
1103
- let [result, references, functionCalls, responded]
1104
- = [options?.result ?? '', null, null];
1159
+ const chat = client.startChat({ history, ...generationConfig(options) });
1160
+ const resp = await chat.sendMessageStream(prompt);
1105
1161
  for await (const chunk of resp.stream) {
1106
1162
  functionCalls || (functionCalls = chunk.functionCalls);
1107
1163
  const rfc = packGeminiReferences(
@@ -1110,12 +1166,17 @@ const promptGemini = async (content, options = {}) => {
1110
1166
  );
1111
1167
  rfc && (references = rfc);
1112
1168
  let delta = chunk?.text?.() || '';
1113
- delta && (responded = responded || (delta = `\n\n${delta}`));
1169
+ options.result && delta
1170
+ && (responded = responded || (delta = `\n\n${delta}`));
1114
1171
  result += delta;
1115
- await streamResp({ text: options?.delta ? delta : result }, options);
1172
+ delta && await streamResp({
1173
+ text: options.delta ? delta : result,
1174
+ }, options);
1116
1175
  }
1117
1176
  const _resp = await resp.response;
1118
- functionCalls = (functionCalls() || _resp.functionCalls() || []).map(x => ({ functionCall: x }));
1177
+ functionCalls = (
1178
+ functionCalls() || _resp.functionCalls() || []
1179
+ ).map(x => ({ functionCall: x }));
1119
1180
  const { toolsResult, toolsResponse } = await handleToolsCall(
1120
1181
  { role: MODEL, parts: functionCalls },
1121
1182
  { ...options, result, flavor: GEMINI }
@@ -1126,7 +1187,7 @@ const promptGemini = async (content, options = {}) => {
1126
1187
  ...toolsResult], result: toolsResponse,
1127
1188
  });
1128
1189
  }
1129
- return await packGptResp({
1190
+ return await packResp({
1130
1191
  text: mergeMsgs(toolsResponse, toolsResult), references,
1131
1192
  }, options);
1132
1193
  };
@@ -1159,9 +1220,9 @@ const createOpenAIEmbedding = async (input, options) => {
1159
1220
  };
1160
1221
 
1161
1222
  const createGeminiEmbedding = async (input, options) => {
1162
- const { embedding } = await getGeminiClient(options);
1223
+ const { client } = await getGeminiClient(options);
1163
1224
  const model = options?.model || DEFAULT_MODELS[GEMINI_EMEDDING];
1164
- const resp = await embedding.embedContent(
1225
+ const resp = await client.getGenerativeModel({ model }).embedContent(
1165
1226
  await checkEmbeddingInput(input, model)
1166
1227
  );
1167
1228
  return options?.raw ? resp : resp?.embedding.values;
@@ -1295,116 +1356,40 @@ const resetSession = async (sessionId, options) => {
1295
1356
 
1296
1357
  const packResult = resp => {
1297
1358
  const result = {
1298
- ...resp, richText: resp.richText || resp.text, spoken: renderText(
1299
- resp.text, { noCode: true, noLink: true }
1359
+ ...resp, spoken: renderText(
1360
+ resp.markdown, { noCode: true, noLink: true }
1300
1361
  ).replace(/\[\^\d\^\]/ig, ''),
1301
1362
  };
1302
- log(`Response (${result.model}): ${JSON.stringify(result.text)}`);
1363
+ log(`Response (${result.model}): ${JSON.stringify(result.markdown)}`);
1303
1364
  // log(result);
1304
1365
  return result;
1305
1366
  };
1306
1367
 
1307
1368
  const talk = async (input, options) => {
1308
- const engine = unifyEngine({
1309
- engine: Object.keys(chatConfig.engines)?.[0] || DEFAULT_MODELS[CHAT],
1310
- ...options,
1311
- });
1369
+ let [engine, chat, resp, sessionId] = [
1370
+ unifyEngine({
1371
+ engine: Object.keys(chatConfig.engines)?.[0] || DEFAULT_MODELS[CHAT],
1372
+ ...options,
1373
+ }), { request: input || ATTACHMENTS }, null,
1374
+ options?.sessionId || newSessionId(),
1375
+ ];
1312
1376
  assert(chatConfig.engines[engine], NOT_INIT);
1313
- const model = options?.model || chatConfig.engines[engine].model;
1314
- const _MODEL = MODELS[model];
1315
- const sessionId = options?.sessionId || newSessionId();
1316
1377
  const session = await getSession(sessionId, { engine, ...options });
1317
- let [resp, sys, messages, msgBuilder] = [null, [], [], null];
1318
- switch (engine) {
1319
- case CHATGPT: case AZURE:
1320
- sys.push(buildGptMessage(session.systemPrompt, { role: system }));
1321
- msgBuilder = () => {
1322
- messages = [];
1323
- session.messages.map(x => {
1324
- messages.push(buildGptMessage(x.request, { role: user }));
1325
- messages.push(buildGptMessage(x.response, { role: assistant }));
1326
- });
1327
- };
1328
- msgBuilder()
1329
- break;
1330
- case GEMINI:
1331
- // already set in the while client initialization:
1332
- // sys.push(buildGeminiHistory(session.systemPrompt, { role: user }));
1333
- msgBuilder = () => {
1334
- messages = [];
1335
- session.messages.map(x => {
1336
- messages.push(buildGeminiHistory(x.request, { role: user }));
1337
- messages.push(buildGeminiHistory(x.response, { role: MODEL }));
1338
- });
1339
- };
1340
- msgBuilder()
1341
- break;
1342
- case CLAUDE:
1343
- sys.push(buildClaudeMessage(session.systemPrompt, { role: system }));
1344
- msgBuilder = () => {
1345
- messages = [];
1346
- session.messages.map(x => {
1347
- messages.push(buildClaudeMessage(x.request, { role: user }));
1348
- messages.push(buildClaudeMessage(x.response, { role: assistant }));
1349
- });
1350
- };
1351
- msgBuilder()
1352
- break;
1353
- case OLLAMA:
1354
- sys.push(buildOllamaMessage(session.systemPrompt, { role: system }));
1355
- msgBuilder = () => {
1356
- messages = [];
1357
- session.messages.map(x => {
1358
- messages.push(buildOllamaMessage(x.request, { role: user }));
1359
- messages.push(buildOllamaMessage(x.response, { role: assistant }));
1360
- });
1361
- };
1362
- msgBuilder()
1363
- break;
1364
- default:
1365
- throwError(`Invalid AI engine: '${engine}'.`);
1366
- }
1367
- await trimPrompt(() => [...sys, ...messages, buildGeminiHistory(
1368
- input || ATTACHMENTS, { role: user } // length hack: ATTACHMENTS
1369
- )], () => {
1370
- if (messages.length) {
1371
- session.messages.shift();
1372
- msgBuilder && msgBuilder();
1373
- } else {
1374
- input = trimTailing(trimTailing(input).slice(0, -1)) + '...';
1375
- }
1376
- }, _MODEL.maxInputTokens - options?.attachments?.length * ATTACHMENT_TOKEN_COST);
1377
- const chat = { request: input || ATTACHMENTS };
1378
- const attachments = [];
1379
- (options?.attachments || []).filter(x => [
1380
- ..._MODEL?.supportedMimeTypes || [], ..._MODEL.supportedAudioTypes || []
1381
- ].includes(x.mime_type)).map(x => attachments.push(x));
1382
1378
  log(`Prompt (${engine}): ${JSON.stringify(input)}`);
1379
+ const pmtOptions = {
1380
+ messages: session.messages, model: chatConfig.engines[engine].model,
1381
+ ...options,
1382
+ };
1383
1383
  switch (engine) {
1384
- case CHATGPT:
1385
- resp = await promptChatGPT(input, {
1386
- messages, model, ...options, attachments,
1387
- });
1388
- break;
1389
- case GEMINI:
1390
- resp = await promptGemini(input, {
1391
- messages, ...options, attachments,
1392
- });
1393
- break;
1394
- case CLAUDE:
1395
- resp = await promptClaude(input, {
1396
- messages, model, ...options, attachments,
1397
- });
1398
- break;
1399
- case OLLAMA:
1400
- resp = await promptOllama(input, { messages, model, ...options });
1401
- break;
1402
- case AZURE:
1403
- resp = await promptAzure(input, { messages, model, ...options });
1404
- break;
1384
+ case CHATGPT: resp = await promptChatGPT(input, pmtOptions); break;
1385
+ case GEMINI: resp = await promptGemini(input, pmtOptions); break;
1386
+ case CLAUDE: resp = await promptClaude(input, pmtOptions); break;
1387
+ case OLLAMA: resp = await promptOllama(input, pmtOptions); break;
1388
+ case AZURE: resp = await promptAzure(input, pmtOptions); break;
1389
+ default: throwError(`Invalid AI engine: '${engine}'.`);
1405
1390
  }
1406
1391
  chat.response = resp.text;
1407
- chat?.request && chat?.response && session.messages.push(chat);
1392
+ chat.request && chat.response && session.messages.push(chat);
1408
1393
  await setSession(sessionId, session, options);
1409
1394
  return { sessionId, ...packResult(resp) };
1410
1395
  };