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/dist/utilitas.lite.mjs +1 -1
- package/dist/utilitas.lite.mjs.map +1 -1
- package/lib/alan.mjs +260 -275
- package/lib/manifest.mjs +1 -1
- package/package.json +1 -1
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
674
|
-
|
|
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
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
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
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
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
|
-
|
|
773
|
-
|
|
774
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
886
|
-
const
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
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
|
|
958
|
+
modalities, audio: options.audio || (
|
|
900
959
|
modalities?.find?.(x => x === AUDIO)
|
|
901
960
|
&& { voice: DEFAULT_MODELS[OPENAI_VOICE], format: 'pcm16' }
|
|
902
|
-
), ...
|
|
903
|
-
|
|
904
|
-
|
|
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
|
-
|
|
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
|
|
932
|
-
await streamResp({
|
|
933
|
-
text: options
|
|
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
|
|
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
|
-
|
|
959
|
-
|
|
960
|
-
|
|
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
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
), LOG);
|
|
1023
|
+
delta && await streamResp({
|
|
1024
|
+
text: options.delta ? delta : result,
|
|
1025
|
+
}, options);
|
|
971
1026
|
}
|
|
972
|
-
|
|
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
|
-
|
|
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:
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
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 [
|
|
998
|
-
text +=
|
|
999
|
-
thinking +=
|
|
1048
|
+
let [deltaThink, deltaText] = [event.thinking || '', event.text || ''];
|
|
1049
|
+
text += deltaText;
|
|
1050
|
+
thinking += deltaThink;
|
|
1000
1051
|
signature = signature || event?.signature || '';
|
|
1001
|
-
|
|
1002
|
-
&& (
|
|
1003
|
-
thinking &&
|
|
1004
|
-
&& (thinkEnd =
|
|
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
|
-
|
|
1011
|
-
result +=
|
|
1012
|
-
await streamResp({
|
|
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
|
|
1078
|
+
...options, toolsResult: [...options.toolsResult || [],
|
|
1026
1079
|
...toolsResult], result: toolsResponse,
|
|
1027
1080
|
});
|
|
1028
1081
|
}
|
|
1029
|
-
return
|
|
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
|
-
|
|
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
|
-
|
|
1087
|
-
|
|
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
|
-
|
|
1169
|
+
options.result && delta
|
|
1170
|
+
&& (responded = responded || (delta = `\n\n${delta}`));
|
|
1114
1171
|
result += delta;
|
|
1115
|
-
await streamResp({
|
|
1172
|
+
delta && await streamResp({
|
|
1173
|
+
text: options.delta ? delta : result,
|
|
1174
|
+
}, options);
|
|
1116
1175
|
}
|
|
1117
1176
|
const _resp = await resp.response;
|
|
1118
|
-
functionCalls = (
|
|
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
|
|
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 {
|
|
1223
|
+
const { client } = await getGeminiClient(options);
|
|
1163
1224
|
const model = options?.model || DEFAULT_MODELS[GEMINI_EMEDDING];
|
|
1164
|
-
const resp = await
|
|
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,
|
|
1299
|
-
resp.
|
|
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.
|
|
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
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
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
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
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
|
|
1392
|
+
chat.request && chat.response && session.messages.push(chat);
|
|
1408
1393
|
await setSession(sessionId, session, options);
|
|
1409
1394
|
return { sessionId, ...packResult(resp) };
|
|
1410
1395
|
};
|