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/dist/utilitas.lite.mjs +1 -1
- package/dist/utilitas.lite.mjs.map +1 -1
- package/lib/alan.mjs +260 -277
- package/lib/manifest.mjs +1 -1
- package/package.json +1 -1
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
674
|
-
|
|
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
|
-
|
|
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');
|
|
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
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
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
|
-
|
|
773
|
-
|
|
774
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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}`);
|
|
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
|
|
956
|
+
modalities, audio: options.audio || (
|
|
900
957
|
modalities?.find?.(x => x === AUDIO)
|
|
901
958
|
&& { voice: DEFAULT_MODELS[OPENAI_VOICE], format: 'pcm16' }
|
|
902
|
-
), ...
|
|
903
|
-
|
|
904
|
-
|
|
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
|
-
|
|
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
|
|
932
|
-
await streamResp({
|
|
933
|
-
text: options
|
|
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
|
|
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
|
-
|
|
959
|
-
|
|
960
|
-
|
|
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
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
), LOG);
|
|
1021
|
+
delta && await streamResp({
|
|
1022
|
+
text: options.delta ? delta : result,
|
|
1023
|
+
}, options);
|
|
971
1024
|
}
|
|
972
|
-
|
|
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
|
-
|
|
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:
|
|
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),
|
|
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 [
|
|
998
|
-
text +=
|
|
999
|
-
thinking +=
|
|
1046
|
+
let [deltaThink, deltaText] = [event.thinking || '', event.text || ''];
|
|
1047
|
+
text += deltaText;
|
|
1048
|
+
thinking += deltaThink;
|
|
1000
1049
|
signature = signature || event?.signature || '';
|
|
1001
|
-
|
|
1002
|
-
&& (
|
|
1003
|
-
thinking &&
|
|
1004
|
-
&& (thinkEnd =
|
|
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
|
-
|
|
1011
|
-
result +=
|
|
1012
|
-
await streamResp({
|
|
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
|
|
1076
|
+
...options, toolsResult: [...options.toolsResult || [],
|
|
1026
1077
|
...toolsResult], result: toolsResponse,
|
|
1027
1078
|
});
|
|
1028
1079
|
}
|
|
1029
|
-
return
|
|
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
|
-
|
|
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
|
-
|
|
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];
|
|
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
|
-
|
|
1167
|
+
options.result && delta
|
|
1168
|
+
&& (responded = responded || (delta = `\n\n${delta}`));
|
|
1114
1169
|
result += delta;
|
|
1115
|
-
await streamResp({
|
|
1170
|
+
delta && await streamResp({
|
|
1171
|
+
text: options.delta ? delta : result,
|
|
1172
|
+
}, options);
|
|
1116
1173
|
}
|
|
1117
1174
|
const _resp = await resp.response;
|
|
1118
|
-
functionCalls = (
|
|
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
|
|
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 {
|
|
1221
|
+
const { client } = await getGeminiClient(options);
|
|
1163
1222
|
const model = options?.model || DEFAULT_MODELS[GEMINI_EMEDDING];
|
|
1164
|
-
const resp = await
|
|
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,
|
|
1299
|
-
resp.
|
|
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.
|
|
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
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
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
|
-
|
|
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;
|
|
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
|
|
1390
|
+
chat.request && chat.response && session.messages.push(chat);
|
|
1408
1391
|
await setSession(sessionId, session, options);
|
|
1409
1392
|
return { sessionId, ...packResult(resp) };
|
|
1410
1393
|
};
|