utilitas 1998.2.38 → 1998.2.40

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
@@ -101,7 +101,6 @@ const clients = {};
101
101
  const size8k = 7680 * 4320;
102
102
  const MAX_TOOL_RECURSION = 10;
103
103
  const LOG = { log: true };
104
- const OPENAI_BASE_URL = 'https://api.openai.com/v1';
105
104
  const sessionType = `${name.toUpperCase()}-SESSION`;
106
105
  const unifyProvider = options => unifyType(options?.provider, 'AI provider');
107
106
  const unifyEngine = options => unifyType(options?.engine, 'AI engine');
@@ -111,7 +110,6 @@ 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
- const packThink = thk => thk ? [`${THINK_STR}\n${thk}\n${THINK_END}`] : [];
115
113
  const countToolCalls = r => r?.split('\n').filter(x => x === TOOLS_STR).length;
116
114
 
117
115
  const DEFAULT_MODELS = {
@@ -498,26 +496,7 @@ const init = async (options) => {
498
496
  if (options?.apiKey) {
499
497
  const { GoogleGenerativeAI } = await need('@google/generative-ai');
500
498
  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
- };
499
+ clients[provider] = { client: genAi };
521
500
  }
522
501
  break;
523
502
  case CLAUDE:
@@ -623,7 +602,7 @@ const buildGeminiParts = (text, attachments) => {
623
602
 
624
603
  const buildGeminiMessage = (content, options) => {
625
604
  content = content || '';
626
- const attachments = (options?.attachments || []).map(x => ({
605
+ const attachments = (options?.attachments?.length ? options.attachments : []).map(x => ({
627
606
  inlineData: { mimeType: x.mime_type, data: x.data }
628
607
  }));
629
608
  return String.isString(content) ? (options?.history ? {
@@ -634,7 +613,7 @@ const buildGeminiMessage = (content, options) => {
634
613
 
635
614
  const buildClaudeMessage = (text, options) => {
636
615
  assert(text, 'Text is required.');
637
- const attachments = (options?.attachments || []).map(x => {
616
+ const attachments = (options?.attachments?.length ? options?.attachments : []).map(x => {
638
617
  let type = '';
639
618
  if ([pdf].includes(x.mime_type)) {
640
619
  type = 'document';
@@ -669,9 +648,38 @@ const listOpenAIModels = async (options) => {
669
648
  return options?.raw ? resp : resp.data;
670
649
  };
671
650
 
651
+ const streamResp = async (resp, options) => {
652
+ const msg = await packResp(resp, { ...options, processing: true });
653
+ return options?.stream && (msg?.text || msg?.audio?.length)
654
+ && await ignoreErrFunc(async () => await options.stream(msg), LOG);
655
+ };
656
+
657
+ const getInfoEnd = text => Math.max(...[THINK_END, TOOLS_END].map(x => {
658
+ const keyEnd = text.indexOf(`${x}\n`);
659
+ return keyEnd >= 0 ? (keyEnd + x.length) : 0;
660
+ }));
661
+
672
662
  const packResp = async (resp, options) => {
673
- let { text: txt, audio, references }
674
- = String.isString(resp) ? { text: resp } : resp;
663
+ if (options?.raw) { return resp; }
664
+ let [
665
+ txt, audio, references, markdown, simpleText, referencesMarkdown, end,
666
+ json
667
+ ] = [
668
+ resp.text // ChatGPT / Claude / Gemini
669
+ || resp?.message?.content || '', // Ollama @tudo: Need to be updated
670
+ resp?.audio?.data, // ChatGPT audio mode
671
+ resp?.references, // Gemini references
672
+ '', '', '', null,
673
+ ];
674
+ markdown = simpleText = txt;
675
+ while ((end = getInfoEnd(simpleText))) {
676
+ simpleText = simpleText.slice(end).trim();
677
+ end = getInfoEnd(simpleText);
678
+ }
679
+ [THINK_STR, TOOLS_STR].map(x => {
680
+ const str = simpleText.indexOf(x);
681
+ str >= 0 && (simpleText = simpleText.slice(0, str).trim());
682
+ });
675
683
  audio && (audio = Buffer.isBuffer(audio) ? audio : await convert(audio, {
676
684
  input: BASE64, expected: BUFFER,
677
685
  })) && audio.length && (audio = Buffer.concat([
@@ -679,6 +687,14 @@ const packResp = async (resp, options) => {
679
687
  ])) && (audio = await convert(audio, {
680
688
  input: BUFFER, expected: BUFFER, ...options || {},
681
689
  }));
690
+ options?.jsonMode && !options?.delta && !options?.processing
691
+ && (json = parseJson(simpleText));
692
+ if (options?.simple && options?.audioMode) { return audio; }
693
+ else if (options?.simple && options?.jsonMode) { return json; }
694
+ else if (options?.simple) { return simpleText; }
695
+ else if (options?.jsonMode) {
696
+ markdown = `\`\`\`json\n${simpleText}\n\`\`\``;
697
+ }
682
698
  // references debug codes:
683
699
  // references = {
684
700
  // "segments": [
@@ -701,77 +717,127 @@ const packResp = async (resp, options) => {
701
717
  // },
702
718
  // ]
703
719
  // };
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');
720
+ if (references?.segments?.length && references?.links?.length) {
721
+ for (let i = references.segments.length - 1; i >= 0; i--) {
722
+ let idx = markdown.indexOf(references.segments[i].text);
723
+ if (idx < 0) { continue; }
724
+ idx += references.segments[i].text.length;
725
+ markdown = markdown.slice(0, idx)
726
+ + references.segments[i].indices.map(y => ` (${y + 1})`).join('')
727
+ + markdown.slice(idx);
720
728
  }
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
- }
729
+ referencesMarkdown = 'References:\n\n' + references.links.map(
730
+ (x, i) => `${i + 1}. [${x.title}](${x.uri})`
731
+ ).join('\n');
732
+ }
733
+ markdown = markdown.split('\n');
734
+ for (let i in markdown) {
735
+ switch (markdown[i]) {
736
+ case THINK_STR: markdown[i] = MD_CODE + THINK; break;
737
+ case TOOLS_STR: markdown[i] = MD_CODE + TOOLS; break;
738
+ case THINK_END: case TOOLS_END: markdown[i] = MD_CODE;
734
739
  }
735
- richText = lines.join('\n').trim();
740
+ }
741
+ markdown = markdown.join('\n');
742
+ if (!options?.delta && !options?.processing) {
743
+ txt = txt.trim();
744
+ markdown = markdown.trim();
736
745
  }
737
746
  return {
738
- ...text(txt), ...options?.jsonMode && !(
739
- options?.delta && options?.processing
740
- ) ? { json: parseJson(txt) } : {},
741
- ...richText ? { richText } : {},
742
- ...references ? { references } : {},
747
+ ...text(txt), ...options?.jsonMode ? { json } : {},
748
+ markdown, ...references ? { references } : {},
743
749
  ...referencesMarkdown ? { referencesMarkdown } : {},
744
750
  ...audio ? { audio, audioMimeType: options?.audioMimeType } : {},
751
+ processing: options?.processing,
745
752
  model: options?.model,
746
753
  };
747
754
  };
748
755
 
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();
756
+ const buildPrompts = async (model, input, options = {}) => {
757
+ assert(!(
758
+ options.jsonMode && !model?.json
759
+ ), `This model does not support JSON output: ${options.model}`);
760
+ assert(!(
761
+ options.reasoning && !model?.reasoning
762
+ ), `This model does not support reasoning: ${options.model}`);
763
+ let [systemPrompt, history, content, prompt, _system, _user, _assistant] = [
764
+ null, null, input || ATTACHMENTS, null, [], null, // length hack: ATTACHMENTS
765
+ { role: system }, { role: user }, { role: assistant }
766
+ ];
767
+ options.systemPrompt = options.systemPrompt || INSTRUCTIONS;
768
+ options.attachments = (
769
+ options.attachments?.length ? options.attachments : []
770
+ ).filter(x => [
771
+ ...model?.supportedMimeTypes || [], ...model.supportedAudioTypes || []
772
+ ].includes(x.mime_type));
773
+ switch (options.flavor) {
774
+ case CHATGPT:
775
+ systemPrompt = buildGptMessage(options.systemPrompt, _system);
776
+ prompt = buildGptMessage(content, options);
777
+ break;
778
+ case CLAUDE:
779
+ systemPrompt = buildClaudeMessage(options.systemPrompt, _system);
780
+ prompt = buildClaudeMessage(content, options)
781
+ break;
782
+ case OLLAMA:
783
+ systemPrompt = buildOllamaMessage(options.systemPrompt, _system);
784
+ prompt = buildOllamaMessage(content, options);
785
+ break;
786
+ case GEMINI:
787
+ systemPrompt = buildGeminiHistory(options.systemPrompt, _system);
788
+ prompt = options.toolsResult?.[options.toolsResult?.length - 1]?.parts
789
+ || buildGeminiMessage(content, options)
790
+ break;
791
+ }
792
+ const msgBuilder = () => {
793
+ history = [];
794
+ (options.messages?.length ? options.messages : []).map(x => {
795
+ switch (options.flavor) {
796
+ case CHATGPT:
797
+ history.push(buildGptMessage(x.request, _user));
798
+ history.push(buildGptMessage(x.response, _assistant));
799
+ break;
800
+ case CLAUDE:
801
+ history.push(buildClaudeMessage(x.request, _user));
802
+ history.push(buildClaudeMessage(x.response, _assistant));
803
+ break;
804
+ case OLLAMA:
805
+ history.push(buildClaudeMessage(x.request, _user));
806
+ history.push(buildClaudeMessage(x.response, _assistant));
807
+ break;
808
+ case GEMINI:
809
+ if (options.attachments?.length) { return; }
810
+ history.push(buildGeminiHistory(x.request, _user));
811
+ history.push(buildGeminiHistory(x.response, { role: MODEL }));
812
+ break;
770
813
  }
814
+ });
815
+ switch (options.flavor) {
816
+ case CHATGPT: case CLAUDE: case OLLAMA:
817
+ history.push(prompt, ...options.toolsResult?.length
818
+ ? options.toolsResult : []);
819
+ history = messages(history);
820
+ break;
821
+ case GEMINI:
822
+ history.push(
823
+ ...options.toolsResult?.length ? [
824
+ buildGeminiHistory(content, { ...options, role: user }),
825
+ ...options.toolsResult.slice(0, options.toolsResult.length - 1)
826
+ ] : []
827
+ );
828
+ break;
771
829
  }
772
- return text;
773
- }
774
- return await packResp({ text, audio, references: resp?.references }, options);
830
+ };
831
+ msgBuilder();
832
+ await trimPrompt(() => [systemPrompt, history, prompt], () => {
833
+ if (options.messages.length) {
834
+ options.messages.shift();
835
+ msgBuilder();
836
+ } else {
837
+ content = trimTailing(trimTailing(content).slice(0, -1)) + '...';
838
+ }
839
+ }, model.maxInputTokens - options.attachments?.length * ATTACHMENT_TOKEN_COST);
840
+ return { systemPrompt, history, prompt };
775
841
  };
776
842
 
777
843
  const handleToolsCall = async (msg, options) => {
@@ -867,43 +933,32 @@ const mergeMsgs = (resp, calls) => [resp, ...calls.length ? [
867
933
  ] : []].map(x => x.trim()).join('\n\n');
868
934
 
869
935
  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) {
936
+ if (options.model) { } else if (options.provider === AZURE) {
877
937
  options.model = DEFAULT_MODELS[AZURE];
878
- } else if (options?.reasoning) {
938
+ } else if (options.reasoning) {
879
939
  options.model = DEFAULT_MODELS[CHATGPT_REASONING];
880
940
  } else {
881
941
  options.model = DEFAULT_MODELS[CHATGPT];
882
942
  }
883
- options?.reasoning && !options?.reasoning_effort
943
+ let [_MODEL, result, resultAudio, event, resultTools, responded] = [
944
+ MODELS[options.model], options?.result ?? '', Buffer.alloc(0), null, [],
945
+ false
946
+ ];
947
+ options.reasoning && !options.reasoning_effort
884
948
  && (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}`);
949
+ const { client } = await getOpenAIClient(options);
950
+ const { history }
951
+ = await buildPrompts(_MODEL, content, { ...options, flavor: CHATGPT });
952
+ const modalities = options.modalities
953
+ || (options.audioMode ? [TEXT, AUDIO] : undefined);
895
954
  [options.audioMimeType, options.suffix] = [pcm16, 'pcm.wav'];
896
- let [result, resultAudio, event, resultTools, responded]
897
- = [options?.result ?? '', Buffer.alloc(0), null, [], false];
898
955
  const resp = await client.chat.completions.create({
899
- modalities, audio: options?.audio || (
956
+ modalities, audio: options.audio || (
900
957
  modalities?.find?.(x => x === AUDIO)
901
958
  && { 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 ? {
959
+ ), ...history, ..._MODEL?.tools ? {
960
+ tools: options.tools ?? tools.map(x => x.def),
961
+ } : {}, ...options.jsonMode ? {
907
962
  response_format: { type: JSON_OBJECT }
908
963
  } : {}, model: options.model, stream: true,
909
964
  store: true, tool_choice: 'auto',
@@ -925,12 +980,13 @@ const promptChatGPT = async (content, options = {}) => {
925
980
  x?.function?.name && (curFunc.function.name += x.function.name);
926
981
  x?.function?.arguments && (curFunc.function.arguments += x.function.arguments);
927
982
  }
928
- deltaText && (responded = responded || (deltaText = `\n\n${deltaText}`));
983
+ options.result && deltaText
984
+ && (responded = responded || (deltaText = `\n\n${deltaText}`));
929
985
  result += deltaText;
930
986
  resultAudio = Buffer.concat([resultAudio, deltaAudio]);
931
- const respAudio = options?.delta ? deltaAudio : resultAudio;
932
- await streamResp({
933
- text: options?.delta ? deltaText : result,
987
+ const respAudio = options.delta ? deltaAudio : resultAudio;
988
+ (deltaText || deltaAudio?.length) && await streamResp({
989
+ text: options.delta ? deltaText : result,
934
990
  ...respAudio.length ? { audio: { data: respAudio } } : {},
935
991
  }, options);
936
992
  }
@@ -944,7 +1000,7 @@ const promptChatGPT = async (content, options = {}) => {
944
1000
  return promptChatGPT(content, { ...options, toolsResult, result: toolsResponse });
945
1001
  }
946
1002
  event.text = mergeMsgs(toolsResponse, toolsResult);
947
- return await packGptResp(event, options);
1003
+ return await packResp(event, options);
948
1004
  };
949
1005
 
950
1006
  const promptAzure = async (content, options = {}) =>
@@ -955,61 +1011,56 @@ const promptOllama = async (content, options = {}) => {
955
1011
  // https://github.com/ollama/ollama-js
956
1012
  // https://github.com/jmorganca/ollama/blob/main/examples/typescript-simplechat/client.ts
957
1013
  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, ''];
1014
+ let [_MODEL, chunk, result] = [MODELS[options.model], null, ''];
1015
+ const { history: h }
1016
+ = await buildPrompts(_MODEL, content, { ...options, flavor: OLLAMA });
1017
+ const resp = await client.chat({ model: options.model, stream: true, ...h });
963
1018
  for await (chunk of resp) {
964
1019
  const delta = chunk.message.content || '';
965
- if (delta === '') { continue; }
966
1020
  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);
1021
+ delta && await streamResp({
1022
+ text: options.delta ? delta : result,
1023
+ }, options);
971
1024
  }
972
- chunk.message.content = result;
973
- return await packGptResp(chunk, options);
1025
+ return await packResp({ text: result }, options);
974
1026
  };
975
1027
 
976
1028
  const promptClaude = async (content, options = {}) => {
1029
+ options.model = options.model || DEFAULT_MODELS[CLAUDE];
1030
+ let [_MODEL, event, text, thinking, signature, result, thinkEnd, tool_use]
1031
+ = [MODELS[options.model], null, '', '', '', options.result ?? '', '', []];
977
1032
  const { client } = await getClaudeClient(options);
978
- options.model = options?.model || DEFAULT_MODELS[CLAUDE];
1033
+ const { history }
1034
+ = await buildPrompts(_MODEL, content, { ...options, flavor: CLAUDE });
979
1035
  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),
1036
+ model: options.model, max_tokens: _MODEL.maxOutputTokens, ...history,
1037
+ stream: true, ...options.reasoning ?? _MODEL?.reasoning ? {
1038
+ thinking: options.thinking || { type: 'enabled', budget_tokens: 1024 },
1039
+ } : {}, ..._MODEL?.tools ? { // https://docs.anthropic.com/en/docs/build-with-claude/extended-thinking
1040
+ tools: options.tools ?? toolsClaude.map(x => x.def),
990
1041
  tool_choice: { type: 'auto' },
991
1042
  } : {},
992
1043
  });
993
- let [event, text, thinking, signature, result, thinkEnd, tool_use]
994
- = [null, '', '', '', options?.result ?? '', '', []];
995
1044
  for await (const chunk of resp) {
996
1045
  event = chunk?.content_block || chunk?.delta || {};
997
- let [thkDelta, txtDelta] = [event.thinking || '', event.text || ''];
998
- text += txtDelta;
999
- thinking += thkDelta;
1046
+ let [deltaThink, deltaText] = [event.thinking || '', event.text || ''];
1047
+ text += deltaText;
1048
+ thinking += deltaThink;
1000
1049
  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`);
1050
+ deltaThink && deltaThink === thinking
1051
+ && (deltaThink = `${THINK_STR}\n${deltaThink}`);
1052
+ thinking && deltaText && !thinkEnd
1053
+ && (thinkEnd = deltaThink = `${deltaThink}\n${THINK_END}\n\n`);
1005
1054
  if (event?.type === 'tool_use') {
1006
1055
  tool_use.push({ ...event, input: '' });
1007
1056
  } else if (event.partial_json) {
1008
1057
  tool_use[tool_use.length - 1].input += event.partial_json;
1009
1058
  }
1010
- txtDelta = thkDelta + txtDelta;
1011
- result += txtDelta;
1012
- await streamResp({ text: options?.delta ? txtDelta : result }, options);
1059
+ deltaText = deltaThink + deltaText;
1060
+ result += deltaText;
1061
+ deltaText && await streamResp({
1062
+ text: options.delta ? deltaText : result,
1063
+ }, options);
1013
1064
  }
1014
1065
  event = {
1015
1066
  role: assistant, content: [
@@ -1022,11 +1073,11 @@ const promptClaude = async (content, options = {}) => {
1022
1073
  );
1023
1074
  if (tool_use.length && countToolCalls(toolsResponse) < MAX_TOOL_RECURSION) {
1024
1075
  return await promptClaude(content, {
1025
- ...options, toolsResult: [...options?.toolsResult || [],
1076
+ ...options, toolsResult: [...options.toolsResult || [],
1026
1077
  ...toolsResult], result: toolsResponse,
1027
1078
  });
1028
1079
  }
1029
- return packGptResp({ text: mergeMsgs(toolsResponse, tool_use) }, options);
1080
+ return packResp({ text: mergeMsgs(toolsResponse, tool_use) }, options);
1030
1081
  };
1031
1082
 
1032
1083
  const uploadFile = async (input, options) => {
@@ -1079,29 +1130,32 @@ const packGeminiReferences = (chunks, supports) => {
1079
1130
  };
1080
1131
 
1081
1132
  const promptGemini = async (content, options = {}) => {
1082
- const { generative, genModel } = await getGeminiClient(options);
1133
+ options.model || (options.model = DEFAULT_MODELS[GEMINI]);
1134
+ let [result, references, functionCalls, responded, _MODEL]
1135
+ = [options.result ?? '', null, null, false, MODELS[options.model]];
1136
+ const { client: _client } = await getGeminiClient(options);
1137
+ const { systemPrompt: systemInstruction, history, prompt }
1138
+ = await buildPrompts(_MODEL, content, { ...options, flavor: GEMINI });
1139
+ const client = _client.getGenerativeModel({
1140
+ model: options.model, systemInstruction,
1141
+ ...MODELS[options.model]?.tools && !options.jsonMode ? (
1142
+ options.tools ?? {
1143
+ tools: [
1144
+ // @todo: Gemini will failed when using these tools together.
1145
+ // https://ai.google.dev/gemini-api/docs/function-calling
1146
+ // { codeExecution: {} },
1147
+ // { googleSearch: {} },
1148
+ { functionDeclarations: toolsGemini.map(x => x.def) },
1149
+ ],
1150
+ toolConfig: { functionCallingConfig: { mode: 'AUTO' } },
1151
+ }
1152
+ ) : {},
1153
+ });
1083
1154
  // https://github.com/google/generative-ai-js/blob/main/samples/node/advanced-chat.js
1084
1155
  // @todo: check this issue similar to Vertex AI:
1085
1156
  // 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];
1157
+ const chat = client.startChat({ history, ...generationConfig(options) });
1158
+ const resp = await chat.sendMessageStream(prompt);
1105
1159
  for await (const chunk of resp.stream) {
1106
1160
  functionCalls || (functionCalls = chunk.functionCalls);
1107
1161
  const rfc = packGeminiReferences(
@@ -1110,12 +1164,17 @@ const promptGemini = async (content, options = {}) => {
1110
1164
  );
1111
1165
  rfc && (references = rfc);
1112
1166
  let delta = chunk?.text?.() || '';
1113
- delta && (responded = responded || (delta = `\n\n${delta}`));
1167
+ options.result && delta
1168
+ && (responded = responded || (delta = `\n\n${delta}`));
1114
1169
  result += delta;
1115
- await streamResp({ text: options?.delta ? delta : result }, options);
1170
+ delta && await streamResp({
1171
+ text: options.delta ? delta : result,
1172
+ }, options);
1116
1173
  }
1117
1174
  const _resp = await resp.response;
1118
- functionCalls = (functionCalls() || _resp.functionCalls() || []).map(x => ({ functionCall: x }));
1175
+ functionCalls = (
1176
+ functionCalls() || _resp.functionCalls() || []
1177
+ ).map(x => ({ functionCall: x }));
1119
1178
  const { toolsResult, toolsResponse } = await handleToolsCall(
1120
1179
  { role: MODEL, parts: functionCalls },
1121
1180
  { ...options, result, flavor: GEMINI }
@@ -1126,7 +1185,7 @@ const promptGemini = async (content, options = {}) => {
1126
1185
  ...toolsResult], result: toolsResponse,
1127
1186
  });
1128
1187
  }
1129
- return await packGptResp({
1188
+ return await packResp({
1130
1189
  text: mergeMsgs(toolsResponse, toolsResult), references,
1131
1190
  }, options);
1132
1191
  };
@@ -1159,9 +1218,9 @@ const createOpenAIEmbedding = async (input, options) => {
1159
1218
  };
1160
1219
 
1161
1220
  const createGeminiEmbedding = async (input, options) => {
1162
- const { embedding } = await getGeminiClient(options);
1221
+ const { client } = await getGeminiClient(options);
1163
1222
  const model = options?.model || DEFAULT_MODELS[GEMINI_EMEDDING];
1164
- const resp = await embedding.embedContent(
1223
+ const resp = await client.getGenerativeModel({ model }).embedContent(
1165
1224
  await checkEmbeddingInput(input, model)
1166
1225
  );
1167
1226
  return options?.raw ? resp : resp?.embedding.values;
@@ -1295,116 +1354,40 @@ const resetSession = async (sessionId, options) => {
1295
1354
 
1296
1355
  const packResult = resp => {
1297
1356
  const result = {
1298
- ...resp, richText: resp.richText || resp.text, spoken: renderText(
1299
- resp.text, { noCode: true, noLink: true }
1357
+ ...resp, spoken: renderText(
1358
+ resp.markdown, { noCode: true, noLink: true }
1300
1359
  ).replace(/\[\^\d\^\]/ig, ''),
1301
1360
  };
1302
- log(`Response (${result.model}): ${JSON.stringify(result.text)}`);
1361
+ log(`Response (${result.model}): ${JSON.stringify(result.markdown)}`);
1303
1362
  // log(result);
1304
1363
  return result;
1305
1364
  };
1306
1365
 
1307
1366
  const talk = async (input, options) => {
1308
- const engine = unifyEngine({
1309
- engine: Object.keys(chatConfig.engines)?.[0] || DEFAULT_MODELS[CHAT],
1310
- ...options,
1311
- });
1367
+ let [engine, chat, resp, sessionId] = [
1368
+ unifyEngine({
1369
+ engine: Object.keys(chatConfig.engines)?.[0] || DEFAULT_MODELS[CHAT],
1370
+ ...options,
1371
+ }), { request: input || ATTACHMENTS }, null,
1372
+ options?.sessionId || newSessionId(),
1373
+ ];
1312
1374
  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
1375
  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
1376
  log(`Prompt (${engine}): ${JSON.stringify(input)}`);
1377
+ const pmtOptions = {
1378
+ messages: session.messages, model: chatConfig.engines[engine].model,
1379
+ ...options,
1380
+ };
1383
1381
  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;
1382
+ case CHATGPT: resp = await promptChatGPT(input, pmtOptions); break;
1383
+ case GEMINI: resp = await promptGemini(input, pmtOptions); break;
1384
+ case CLAUDE: resp = await promptClaude(input, pmtOptions); break;
1385
+ case OLLAMA: resp = await promptOllama(input, pmtOptions); break;
1386
+ case AZURE: resp = await promptAzure(input, pmtOptions); break;
1387
+ default: throwError(`Invalid AI engine: '${engine}'.`);
1405
1388
  }
1406
1389
  chat.response = resp.text;
1407
- chat?.request && chat?.response && session.messages.push(chat);
1390
+ chat.request && chat.response && session.messages.push(chat);
1408
1391
  await setSession(sessionId, session, options);
1409
1392
  return { sessionId, ...packResult(resp) };
1410
1393
  };