utilitas 1998.2.37 → 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 +286 -290
- package/lib/manifest.mjs +1 -1
- package/lib/shot.mjs +21 -58
- package/package.json +1 -1
package/lib/alan.mjs
CHANGED
|
@@ -54,7 +54,7 @@ const [
|
|
|
54
54
|
CLAUDE_35_HAIKU, CLOUD_37_SONNET, AUDIO, WAV, CHATGPT_MINI, ATTACHMENTS,
|
|
55
55
|
CHAT, OPENAI_VOICE, MEDIUM, LOW, HIGH, GPT_REASONING_EFFORT, THINK,
|
|
56
56
|
THINK_STR, THINK_END, AZURE, TOOLS_STR, TOOLS_END, TOOLS, TEXT, THINKING,
|
|
57
|
-
OK,
|
|
57
|
+
OK, FUNC,
|
|
58
58
|
] = [
|
|
59
59
|
'OPENAI', 'GEMINI', 'CHATGPT', 'OPENAI_EMBEDDING', 'GEMINI_EMEDDING',
|
|
60
60
|
'OPENAI_TRAINING', 'OLLAMA', 'CLAUDE', 'gpt-4o-mini', 'gpt-4o', 'o1',
|
|
@@ -66,7 +66,7 @@ const [
|
|
|
66
66
|
'claude-3-7-sonnet@20250219', 'audio', 'wav', 'CHATGPT_MINI',
|
|
67
67
|
'[ATTACHMENTS]', 'CHAT', 'OPENAI_VOICE', 'medium', 'low', 'high',
|
|
68
68
|
'medium', 'think', '<think>', '</think>', 'AZURE', '<tools>',
|
|
69
|
-
'</tools>', 'tools', 'text', 'thinking', 'OK',
|
|
69
|
+
'</tools>', 'tools', 'text', 'thinking', 'OK', 'function',
|
|
70
70
|
];
|
|
71
71
|
|
|
72
72
|
const [
|
|
@@ -90,7 +90,7 @@ const [tool, provider, messages, text] = [
|
|
|
90
90
|
const [name, user, system, assistant, MODEL, JSON_OBJECT, TOOL, silent]
|
|
91
91
|
= ['Alan', 'user', 'system', 'assistant', 'model', 'json_object', 'tool', true];
|
|
92
92
|
const [CODE_INTERPRETER, RETRIEVAL, FUNCTION]
|
|
93
|
-
= ['code_interpreter', 'retrieval',
|
|
93
|
+
= ['code_interpreter', 'retrieval', FUNC].map(tool);
|
|
94
94
|
const [NOT_INIT, INVALID_FILE]
|
|
95
95
|
= ['AI engine has not been initialized.', 'Invalid file data.'];
|
|
96
96
|
const chatConfig
|
|
@@ -442,7 +442,10 @@ const tools = [
|
|
|
442
442
|
parameters: {
|
|
443
443
|
type: 'object',
|
|
444
444
|
properties: {
|
|
445
|
-
keyword: { type: 'string', description: 'The keyword you need to search for.' }
|
|
445
|
+
keyword: { type: 'string', description: 'The keyword you need to search for.' },
|
|
446
|
+
num: { type: 'integer', description: 'The number of search results you need, default `10`.' },
|
|
447
|
+
start: { type: 'integer', description: 'The start index of the search results, default `1`.' },
|
|
448
|
+
image: { type: 'boolean', description: 'Whether to search for images, default `false`.' },
|
|
446
449
|
},
|
|
447
450
|
required: ['keyword'],
|
|
448
451
|
additionalProperties: false
|
|
@@ -495,26 +498,7 @@ const init = async (options) => {
|
|
|
495
498
|
if (options?.apiKey) {
|
|
496
499
|
const { GoogleGenerativeAI } = await need('@google/generative-ai');
|
|
497
500
|
const genAi = new GoogleGenerativeAI(options.apiKey);
|
|
498
|
-
|
|
499
|
-
clients[provider] = {
|
|
500
|
-
generative: genAi.getGenerativeModel({
|
|
501
|
-
model: genModel,
|
|
502
|
-
systemInstruction: { role: system, parts: [{ text: INSTRUCTIONS }] },
|
|
503
|
-
...MODELS[genModel]?.tools ? (options?.tools ?? {
|
|
504
|
-
tools: [
|
|
505
|
-
// @todo: Gemini will failed when using these tools together.
|
|
506
|
-
// https://ai.google.dev/gemini-api/docs/function-calling
|
|
507
|
-
// { codeExecution: {} },
|
|
508
|
-
// { googleSearch: {} },
|
|
509
|
-
{ functionDeclarations: toolsGemini.map(x => x.def) },
|
|
510
|
-
],
|
|
511
|
-
toolConfig: { functionCallingConfig: { mode: 'AUTO' } },
|
|
512
|
-
}) : {},
|
|
513
|
-
}),
|
|
514
|
-
embedding: genAi.getGenerativeModel({
|
|
515
|
-
model: DEFAULT_MODELS[GEMINI_EMEDDING],
|
|
516
|
-
}), genModel,
|
|
517
|
-
};
|
|
501
|
+
clients[provider] = { client: genAi };
|
|
518
502
|
}
|
|
519
503
|
break;
|
|
520
504
|
case CLAUDE:
|
|
@@ -620,7 +604,7 @@ const buildGeminiParts = (text, attachments) => {
|
|
|
620
604
|
|
|
621
605
|
const buildGeminiMessage = (content, options) => {
|
|
622
606
|
content = content || '';
|
|
623
|
-
const attachments = (options?.attachments
|
|
607
|
+
const attachments = (options?.attachments?.length ? options.attachments : []).map(x => ({
|
|
624
608
|
inlineData: { mimeType: x.mime_type, data: x.data }
|
|
625
609
|
}));
|
|
626
610
|
return String.isString(content) ? (options?.history ? {
|
|
@@ -631,7 +615,7 @@ const buildGeminiMessage = (content, options) => {
|
|
|
631
615
|
|
|
632
616
|
const buildClaudeMessage = (text, options) => {
|
|
633
617
|
assert(text, 'Text is required.');
|
|
634
|
-
const attachments = (options?.attachments
|
|
618
|
+
const attachments = (options?.attachments?.length ? options?.attachments : []).map(x => {
|
|
635
619
|
let type = '';
|
|
636
620
|
if ([pdf].includes(x.mime_type)) {
|
|
637
621
|
type = 'document';
|
|
@@ -666,9 +650,38 @@ const listOpenAIModels = async (options) => {
|
|
|
666
650
|
return options?.raw ? resp : resp.data;
|
|
667
651
|
};
|
|
668
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
|
+
|
|
669
664
|
const packResp = async (resp, options) => {
|
|
670
|
-
|
|
671
|
-
|
|
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
|
+
});
|
|
672
685
|
audio && (audio = Buffer.isBuffer(audio) ? audio : await convert(audio, {
|
|
673
686
|
input: BASE64, expected: BUFFER,
|
|
674
687
|
})) && audio.length && (audio = Buffer.concat([
|
|
@@ -676,6 +689,14 @@ const packResp = async (resp, options) => {
|
|
|
676
689
|
])) && (audio = await convert(audio, {
|
|
677
690
|
input: BUFFER, expected: BUFFER, ...options || {},
|
|
678
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
|
+
}
|
|
679
700
|
// references debug codes:
|
|
680
701
|
// references = {
|
|
681
702
|
// "segments": [
|
|
@@ -698,77 +719,127 @@ const packResp = async (resp, options) => {
|
|
|
698
719
|
// },
|
|
699
720
|
// ]
|
|
700
721
|
// };
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
idx += references.segments[i].text.length;
|
|
710
|
-
richText = richText.slice(0, idx)
|
|
711
|
-
+ references.segments[i].indices.map(y => ` (${y + 1})`).join('')
|
|
712
|
-
+ richText.slice(idx);
|
|
713
|
-
}
|
|
714
|
-
referencesMarkdown = 'References:\n\n' + references.links.map((x, i) => {
|
|
715
|
-
return `${i + 1}. [${x.title}](${x.uri})`;
|
|
716
|
-
}).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);
|
|
717
730
|
}
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
case TOOLS_END:
|
|
729
|
-
lines[i] = MD_CODE;
|
|
730
|
-
}
|
|
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;
|
|
731
741
|
}
|
|
732
|
-
|
|
742
|
+
}
|
|
743
|
+
markdown = markdown.join('\n');
|
|
744
|
+
if (!options?.delta && !options?.processing) {
|
|
745
|
+
txt = txt.trim();
|
|
746
|
+
markdown = markdown.trim();
|
|
733
747
|
}
|
|
734
748
|
return {
|
|
735
|
-
...text(txt), ...options?.jsonMode
|
|
736
|
-
|
|
737
|
-
) ? { json: parseJson(txt) } : {},
|
|
738
|
-
...richText ? { richText } : {},
|
|
739
|
-
...references ? { references } : {},
|
|
749
|
+
...text(txt), ...options?.jsonMode ? { json } : {},
|
|
750
|
+
markdown, ...references ? { references } : {},
|
|
740
751
|
...referencesMarkdown ? { referencesMarkdown } : {},
|
|
741
752
|
...audio ? { audio, audioMimeType: options?.audioMimeType } : {},
|
|
753
|
+
processing: options?.processing,
|
|
742
754
|
model: options?.model,
|
|
743
755
|
};
|
|
744
756
|
};
|
|
745
757
|
|
|
746
|
-
const
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
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;
|
|
767
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;
|
|
768
831
|
}
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
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 };
|
|
772
843
|
};
|
|
773
844
|
|
|
774
845
|
const handleToolsCall = async (msg, options) => {
|
|
@@ -792,9 +863,11 @@ const handleToolsCall = async (msg, options) => {
|
|
|
792
863
|
for (const fn of calls) {
|
|
793
864
|
switch (options?.flavor) {
|
|
794
865
|
case CLAUDE:
|
|
795
|
-
input = fn.input = String.isString(fn?.input)
|
|
866
|
+
input = fn.input = String.isString(fn?.input)
|
|
867
|
+
? parseJson(fn.input) : fn?.input;
|
|
796
868
|
packMsg = (content, is_error) => ({
|
|
797
|
-
type: 'tool_result', tool_use_id: fn.id,
|
|
869
|
+
type: 'tool_result', tool_use_id: fn.id,
|
|
870
|
+
content, is_error,
|
|
798
871
|
});
|
|
799
872
|
break;
|
|
800
873
|
case GEMINI:
|
|
@@ -802,7 +875,8 @@ const handleToolsCall = async (msg, options) => {
|
|
|
802
875
|
packMsg = (t, e) => ({
|
|
803
876
|
functionResponse: {
|
|
804
877
|
name: fn?.functionCall?.name, response: {
|
|
805
|
-
name: fn?.functionCall?.name,
|
|
878
|
+
name: fn?.functionCall?.name,
|
|
879
|
+
content: e ? `[Error] ${t}` : t,
|
|
806
880
|
}
|
|
807
881
|
}
|
|
808
882
|
});
|
|
@@ -842,13 +916,18 @@ const handleToolsCall = async (msg, options) => {
|
|
|
842
916
|
log(rt);
|
|
843
917
|
}
|
|
844
918
|
}
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
919
|
+
if (content.length) {
|
|
920
|
+
switch (options?.flavor) {
|
|
921
|
+
case CLAUDE: content = [{ role: user, content }]; break;
|
|
922
|
+
case GEMINI: content = [{ role: FUNC, parts: content }]; break;
|
|
923
|
+
}
|
|
848
924
|
}
|
|
849
925
|
responded && await resp(`\n${TOOLS_END}`);
|
|
850
926
|
}
|
|
851
|
-
return {
|
|
927
|
+
return {
|
|
928
|
+
toolsResult: [...content.length ? preRes : [], ...content],
|
|
929
|
+
toolsResponse,
|
|
930
|
+
};
|
|
852
931
|
};
|
|
853
932
|
|
|
854
933
|
const mergeMsgs = (resp, calls) => [resp, ...calls.length ? [
|
|
@@ -856,43 +935,32 @@ const mergeMsgs = (resp, calls) => [resp, ...calls.length ? [
|
|
|
856
935
|
] : []].map(x => x.trim()).join('\n\n');
|
|
857
936
|
|
|
858
937
|
const promptChatGPT = async (content, options = {}) => {
|
|
859
|
-
|
|
860
|
-
// https://github.com/openai/openai-node?tab=readme-ov-file#streaming-responses
|
|
861
|
-
// custom api endpoint not supported vision apis @todo by @Leask
|
|
862
|
-
// Structured Outputs: https://openai.com/index/introducing-structured-outputs-in-the-api/
|
|
863
|
-
client.baseURL !== OPENAI_BASE_URL
|
|
864
|
-
&& options?.attachments?.length && (options.attachments = []);
|
|
865
|
-
if (options?.model) { } else if (options?.provider === AZURE) {
|
|
938
|
+
if (options.model) { } else if (options.provider === AZURE) {
|
|
866
939
|
options.model = DEFAULT_MODELS[AZURE];
|
|
867
|
-
} else if (options
|
|
940
|
+
} else if (options.reasoning) {
|
|
868
941
|
options.model = DEFAULT_MODELS[CHATGPT_REASONING];
|
|
869
942
|
} else {
|
|
870
943
|
options.model = DEFAULT_MODELS[CHATGPT];
|
|
871
944
|
}
|
|
872
|
-
|
|
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
|
|
873
950
|
&& (options.reasoning_effort = GPT_REASONING_EFFORT);
|
|
874
|
-
const
|
|
875
|
-
const
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
options?.jsonMode && !MODELS[options.model]?.json
|
|
880
|
-
), `This model does not support JSON output: ${options.model}`);
|
|
881
|
-
assert(!(
|
|
882
|
-
options?.reasoning && !MODELS[options.model]?.reasoning
|
|
883
|
-
), `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);
|
|
884
956
|
[options.audioMimeType, options.suffix] = [pcm16, 'pcm.wav'];
|
|
885
|
-
let [result, resultAudio, event, resultTools, responded]
|
|
886
|
-
= [options?.result ?? '', Buffer.alloc(0), null, [], false];
|
|
887
957
|
const resp = await client.chat.completions.create({
|
|
888
|
-
modalities, audio: options
|
|
958
|
+
modalities, audio: options.audio || (
|
|
889
959
|
modalities?.find?.(x => x === AUDIO)
|
|
890
960
|
&& { voice: DEFAULT_MODELS[OPENAI_VOICE], format: 'pcm16' }
|
|
891
|
-
), ...
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
tools: options?.tools ?? tools.map(x => x.def),
|
|
895
|
-
} : {}, ...options?.jsonMode ? {
|
|
961
|
+
), ...history, ..._MODEL?.tools ? {
|
|
962
|
+
tools: options.tools ?? tools.map(x => x.def),
|
|
963
|
+
} : {}, ...options.jsonMode ? {
|
|
896
964
|
response_format: { type: JSON_OBJECT }
|
|
897
965
|
} : {}, model: options.model, stream: true,
|
|
898
966
|
store: true, tool_choice: 'auto',
|
|
@@ -914,12 +982,13 @@ const promptChatGPT = async (content, options = {}) => {
|
|
|
914
982
|
x?.function?.name && (curFunc.function.name += x.function.name);
|
|
915
983
|
x?.function?.arguments && (curFunc.function.arguments += x.function.arguments);
|
|
916
984
|
}
|
|
917
|
-
|
|
985
|
+
options.result && deltaText
|
|
986
|
+
&& (responded = responded || (deltaText = `\n\n${deltaText}`));
|
|
918
987
|
result += deltaText;
|
|
919
988
|
resultAudio = Buffer.concat([resultAudio, deltaAudio]);
|
|
920
|
-
const respAudio = options
|
|
921
|
-
await streamResp({
|
|
922
|
-
text: options
|
|
989
|
+
const respAudio = options.delta ? deltaAudio : resultAudio;
|
|
990
|
+
(deltaText || deltaAudio?.length) && await streamResp({
|
|
991
|
+
text: options.delta ? deltaText : result,
|
|
923
992
|
...respAudio.length ? { audio: { data: respAudio } } : {},
|
|
924
993
|
}, options);
|
|
925
994
|
}
|
|
@@ -933,7 +1002,7 @@ const promptChatGPT = async (content, options = {}) => {
|
|
|
933
1002
|
return promptChatGPT(content, { ...options, toolsResult, result: toolsResponse });
|
|
934
1003
|
}
|
|
935
1004
|
event.text = mergeMsgs(toolsResponse, toolsResult);
|
|
936
|
-
return await
|
|
1005
|
+
return await packResp(event, options);
|
|
937
1006
|
};
|
|
938
1007
|
|
|
939
1008
|
const promptAzure = async (content, options = {}) =>
|
|
@@ -944,61 +1013,56 @@ const promptOllama = async (content, options = {}) => {
|
|
|
944
1013
|
// https://github.com/ollama/ollama-js
|
|
945
1014
|
// https://github.com/jmorganca/ollama/blob/main/examples/typescript-simplechat/client.ts
|
|
946
1015
|
options.model = options?.model || model;
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
})
|
|
951
|
-
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 });
|
|
952
1020
|
for await (chunk of resp) {
|
|
953
1021
|
const delta = chunk.message.content || '';
|
|
954
|
-
if (delta === '') { continue; }
|
|
955
1022
|
result += delta;
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
), LOG);
|
|
1023
|
+
delta && await streamResp({
|
|
1024
|
+
text: options.delta ? delta : result,
|
|
1025
|
+
}, options);
|
|
960
1026
|
}
|
|
961
|
-
|
|
962
|
-
return await packGptResp(chunk, options);
|
|
1027
|
+
return await packResp({ text: result }, options);
|
|
963
1028
|
};
|
|
964
1029
|
|
|
965
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 ?? '', '', []];
|
|
966
1034
|
const { client } = await getClaudeClient(options);
|
|
967
|
-
|
|
1035
|
+
const { history }
|
|
1036
|
+
= await buildPrompts(_MODEL, content, { ...options, flavor: CLAUDE });
|
|
968
1037
|
const resp = await client.messages.create({
|
|
969
|
-
model: options.model, max_tokens:
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
...options?.reasoning ?? MODELS[options.model]?.reasoning ? {
|
|
975
|
-
thinking: options?.thinking || { type: 'enabled', budget_tokens: 1024 },
|
|
976
|
-
} : {}, // https://docs.anthropic.com/en/docs/build-with-claude/extended-thinking
|
|
977
|
-
...MODELS[options.model]?.tools ? {
|
|
978
|
-
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),
|
|
979
1043
|
tool_choice: { type: 'auto' },
|
|
980
1044
|
} : {},
|
|
981
1045
|
});
|
|
982
|
-
let [event, text, thinking, signature, result, thinkEnd, tool_use]
|
|
983
|
-
= [null, '', '', '', options?.result ?? '', '', []];
|
|
984
1046
|
for await (const chunk of resp) {
|
|
985
1047
|
event = chunk?.content_block || chunk?.delta || {};
|
|
986
|
-
let [
|
|
987
|
-
text +=
|
|
988
|
-
thinking +=
|
|
1048
|
+
let [deltaThink, deltaText] = [event.thinking || '', event.text || ''];
|
|
1049
|
+
text += deltaText;
|
|
1050
|
+
thinking += deltaThink;
|
|
989
1051
|
signature = signature || event?.signature || '';
|
|
990
|
-
|
|
991
|
-
&& (
|
|
992
|
-
thinking &&
|
|
993
|
-
&& (thinkEnd =
|
|
1052
|
+
deltaThink && deltaThink === thinking
|
|
1053
|
+
&& (deltaThink = `${THINK_STR}\n${deltaThink}`);
|
|
1054
|
+
thinking && deltaText && !thinkEnd
|
|
1055
|
+
&& (thinkEnd = deltaThink = `${deltaThink}\n${THINK_END}\n\n`);
|
|
994
1056
|
if (event?.type === 'tool_use') {
|
|
995
1057
|
tool_use.push({ ...event, input: '' });
|
|
996
1058
|
} else if (event.partial_json) {
|
|
997
1059
|
tool_use[tool_use.length - 1].input += event.partial_json;
|
|
998
1060
|
}
|
|
999
|
-
|
|
1000
|
-
result +=
|
|
1001
|
-
await streamResp({
|
|
1061
|
+
deltaText = deltaThink + deltaText;
|
|
1062
|
+
result += deltaText;
|
|
1063
|
+
deltaText && await streamResp({
|
|
1064
|
+
text: options.delta ? deltaText : result,
|
|
1065
|
+
}, options);
|
|
1002
1066
|
}
|
|
1003
1067
|
event = {
|
|
1004
1068
|
role: assistant, content: [
|
|
@@ -1011,11 +1075,11 @@ const promptClaude = async (content, options = {}) => {
|
|
|
1011
1075
|
);
|
|
1012
1076
|
if (tool_use.length && countToolCalls(toolsResponse) < MAX_TOOL_RECURSION) {
|
|
1013
1077
|
return await promptClaude(content, {
|
|
1014
|
-
...options, toolsResult: [...options
|
|
1078
|
+
...options, toolsResult: [...options.toolsResult || [],
|
|
1015
1079
|
...toolsResult], result: toolsResponse,
|
|
1016
1080
|
});
|
|
1017
1081
|
}
|
|
1018
|
-
return
|
|
1082
|
+
return packResp({ text: mergeMsgs(toolsResponse, tool_use) }, options);
|
|
1019
1083
|
};
|
|
1020
1084
|
|
|
1021
1085
|
const uploadFile = async (input, options) => {
|
|
@@ -1068,55 +1132,63 @@ const packGeminiReferences = (chunks, supports) => {
|
|
|
1068
1132
|
};
|
|
1069
1133
|
|
|
1070
1134
|
const promptGemini = async (content, options = {}) => {
|
|
1071
|
-
|
|
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
|
+
});
|
|
1072
1156
|
// https://github.com/google/generative-ai-js/blob/main/samples/node/advanced-chat.js
|
|
1073
1157
|
// @todo: check this issue similar to Vertex AI:
|
|
1074
1158
|
// Google's bug: history is not allowed while using inline_data?
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
), `This model does not support JSON output: ${genModel} `);
|
|
1078
|
-
options.model = genModel;
|
|
1079
|
-
const chat = generative.startChat({
|
|
1080
|
-
history: [
|
|
1081
|
-
...options?.messages && !options?.attachments?.length ? options.messages : [],
|
|
1082
|
-
...options?.toolsResult ? [
|
|
1083
|
-
buildGeminiMessage(content, { ...options, history: true }),
|
|
1084
|
-
...options.toolsResult.slice(0, options.toolsResult.length - 1)
|
|
1085
|
-
] : []
|
|
1086
|
-
], ...generationConfig(options),
|
|
1087
|
-
});
|
|
1088
|
-
const resp = await chat.sendMessageStream(
|
|
1089
|
-
options?.toolsResult?.[options?.toolsResult?.length]?.parts
|
|
1090
|
-
|| buildGeminiMessage(content, options)
|
|
1091
|
-
);
|
|
1092
|
-
let [result, references, functionCalls]
|
|
1093
|
-
= [options?.result ?? '', null, null];
|
|
1159
|
+
const chat = client.startChat({ history, ...generationConfig(options) });
|
|
1160
|
+
const resp = await chat.sendMessageStream(prompt);
|
|
1094
1161
|
for await (const chunk of resp.stream) {
|
|
1095
1162
|
functionCalls || (functionCalls = chunk.functionCalls);
|
|
1096
|
-
const delta = chunk?.text?.() || '';
|
|
1097
1163
|
const rfc = packGeminiReferences(
|
|
1098
1164
|
chunk.candidates[0]?.groundingMetadata?.groundingChunks,
|
|
1099
1165
|
chunk.candidates[0]?.groundingMetadata?.groundingSupports
|
|
1100
1166
|
);
|
|
1101
|
-
|
|
1167
|
+
rfc && (references = rfc);
|
|
1168
|
+
let delta = chunk?.text?.() || '';
|
|
1169
|
+
options.result && delta
|
|
1170
|
+
&& (responded = responded || (delta = `\n\n${delta}`));
|
|
1102
1171
|
result += delta;
|
|
1103
|
-
|
|
1104
|
-
|
|
1172
|
+
delta && await streamResp({
|
|
1173
|
+
text: options.delta ? delta : result,
|
|
1174
|
+
}, options);
|
|
1105
1175
|
}
|
|
1106
1176
|
const _resp = await resp.response;
|
|
1107
|
-
functionCalls = (
|
|
1177
|
+
functionCalls = (
|
|
1178
|
+
functionCalls() || _resp.functionCalls() || []
|
|
1179
|
+
).map(x => ({ functionCall: x }));
|
|
1108
1180
|
const { toolsResult, toolsResponse } = await handleToolsCall(
|
|
1109
1181
|
{ role: MODEL, parts: functionCalls },
|
|
1110
1182
|
{ ...options, result, flavor: GEMINI }
|
|
1111
1183
|
);
|
|
1112
|
-
if (
|
|
1184
|
+
if (toolsResult.length && countToolCalls(toolsResponse) < MAX_TOOL_RECURSION) {
|
|
1113
1185
|
return promptGemini(content, {
|
|
1114
1186
|
...options || {}, toolsResult: [...options?.toolsResult || [],
|
|
1115
1187
|
...toolsResult], result: toolsResponse,
|
|
1116
1188
|
});
|
|
1117
1189
|
}
|
|
1118
|
-
return await
|
|
1119
|
-
text: mergeMsgs(toolsResponse,
|
|
1190
|
+
return await packResp({
|
|
1191
|
+
text: mergeMsgs(toolsResponse, toolsResult), references,
|
|
1120
1192
|
}, options);
|
|
1121
1193
|
};
|
|
1122
1194
|
|
|
@@ -1148,9 +1220,9 @@ const createOpenAIEmbedding = async (input, options) => {
|
|
|
1148
1220
|
};
|
|
1149
1221
|
|
|
1150
1222
|
const createGeminiEmbedding = async (input, options) => {
|
|
1151
|
-
const {
|
|
1223
|
+
const { client } = await getGeminiClient(options);
|
|
1152
1224
|
const model = options?.model || DEFAULT_MODELS[GEMINI_EMEDDING];
|
|
1153
|
-
const resp = await
|
|
1225
|
+
const resp = await client.getGenerativeModel({ model }).embedContent(
|
|
1154
1226
|
await checkEmbeddingInput(input, model)
|
|
1155
1227
|
);
|
|
1156
1228
|
return options?.raw ? resp : resp?.embedding.values;
|
|
@@ -1284,116 +1356,40 @@ const resetSession = async (sessionId, options) => {
|
|
|
1284
1356
|
|
|
1285
1357
|
const packResult = resp => {
|
|
1286
1358
|
const result = {
|
|
1287
|
-
...resp,
|
|
1288
|
-
resp.
|
|
1359
|
+
...resp, spoken: renderText(
|
|
1360
|
+
resp.markdown, { noCode: true, noLink: true }
|
|
1289
1361
|
).replace(/\[\^\d\^\]/ig, ''),
|
|
1290
1362
|
};
|
|
1291
|
-
log(`Response (${result.model}): ${JSON.stringify(result.
|
|
1363
|
+
log(`Response (${result.model}): ${JSON.stringify(result.markdown)}`);
|
|
1292
1364
|
// log(result);
|
|
1293
1365
|
return result;
|
|
1294
1366
|
};
|
|
1295
1367
|
|
|
1296
1368
|
const talk = async (input, options) => {
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
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
|
+
];
|
|
1301
1376
|
assert(chatConfig.engines[engine], NOT_INIT);
|
|
1302
|
-
const model = options?.model || chatConfig.engines[engine].model;
|
|
1303
|
-
const _MODEL = MODELS[model];
|
|
1304
|
-
const sessionId = options?.sessionId || newSessionId();
|
|
1305
1377
|
const session = await getSession(sessionId, { engine, ...options });
|
|
1306
|
-
let [resp, sys, messages, msgBuilder] = [null, [], [], null];
|
|
1307
|
-
switch (engine) {
|
|
1308
|
-
case CHATGPT: case AZURE:
|
|
1309
|
-
sys.push(buildGptMessage(session.systemPrompt, { role: system }));
|
|
1310
|
-
msgBuilder = () => {
|
|
1311
|
-
messages = [];
|
|
1312
|
-
session.messages.map(x => {
|
|
1313
|
-
messages.push(buildGptMessage(x.request, { role: user }));
|
|
1314
|
-
messages.push(buildGptMessage(x.response, { role: assistant }));
|
|
1315
|
-
});
|
|
1316
|
-
};
|
|
1317
|
-
msgBuilder()
|
|
1318
|
-
break;
|
|
1319
|
-
case GEMINI:
|
|
1320
|
-
// already set in the while client initialization:
|
|
1321
|
-
// sys.push(buildGeminiHistory(session.systemPrompt, { role: user }));
|
|
1322
|
-
msgBuilder = () => {
|
|
1323
|
-
messages = [];
|
|
1324
|
-
session.messages.map(x => {
|
|
1325
|
-
messages.push(buildGeminiHistory(x.request, { role: user }));
|
|
1326
|
-
messages.push(buildGeminiHistory(x.response, { role: MODEL }));
|
|
1327
|
-
});
|
|
1328
|
-
};
|
|
1329
|
-
msgBuilder()
|
|
1330
|
-
break;
|
|
1331
|
-
case CLAUDE:
|
|
1332
|
-
sys.push(buildClaudeMessage(session.systemPrompt, { role: system }));
|
|
1333
|
-
msgBuilder = () => {
|
|
1334
|
-
messages = [];
|
|
1335
|
-
session.messages.map(x => {
|
|
1336
|
-
messages.push(buildClaudeMessage(x.request, { role: user }));
|
|
1337
|
-
messages.push(buildClaudeMessage(x.response, { role: assistant }));
|
|
1338
|
-
});
|
|
1339
|
-
};
|
|
1340
|
-
msgBuilder()
|
|
1341
|
-
break;
|
|
1342
|
-
case OLLAMA:
|
|
1343
|
-
sys.push(buildOllamaMessage(session.systemPrompt, { role: system }));
|
|
1344
|
-
msgBuilder = () => {
|
|
1345
|
-
messages = [];
|
|
1346
|
-
session.messages.map(x => {
|
|
1347
|
-
messages.push(buildOllamaMessage(x.request, { role: user }));
|
|
1348
|
-
messages.push(buildOllamaMessage(x.response, { role: assistant }));
|
|
1349
|
-
});
|
|
1350
|
-
};
|
|
1351
|
-
msgBuilder()
|
|
1352
|
-
break;
|
|
1353
|
-
default:
|
|
1354
|
-
throwError(`Invalid AI engine: '${engine}'.`);
|
|
1355
|
-
}
|
|
1356
|
-
await trimPrompt(() => [...sys, ...messages, buildGeminiHistory(
|
|
1357
|
-
input || ATTACHMENTS, { role: user } // length hack: ATTACHMENTS
|
|
1358
|
-
)], () => {
|
|
1359
|
-
if (messages.length) {
|
|
1360
|
-
session.messages.shift();
|
|
1361
|
-
msgBuilder && msgBuilder();
|
|
1362
|
-
} else {
|
|
1363
|
-
input = trimTailing(trimTailing(input).slice(0, -1)) + '...';
|
|
1364
|
-
}
|
|
1365
|
-
}, _MODEL.maxInputTokens - options?.attachments?.length * ATTACHMENT_TOKEN_COST);
|
|
1366
|
-
const chat = { request: input || ATTACHMENTS };
|
|
1367
|
-
const attachments = [];
|
|
1368
|
-
(options?.attachments || []).filter(x => [
|
|
1369
|
-
..._MODEL?.supportedMimeTypes || [], ..._MODEL.supportedAudioTypes || []
|
|
1370
|
-
].includes(x.mime_type)).map(x => attachments.push(x));
|
|
1371
1378
|
log(`Prompt (${engine}): ${JSON.stringify(input)}`);
|
|
1379
|
+
const pmtOptions = {
|
|
1380
|
+
messages: session.messages, model: chatConfig.engines[engine].model,
|
|
1381
|
+
...options,
|
|
1382
|
+
};
|
|
1372
1383
|
switch (engine) {
|
|
1373
|
-
case CHATGPT:
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
resp = await promptGemini(input, {
|
|
1380
|
-
messages, ...options, attachments,
|
|
1381
|
-
});
|
|
1382
|
-
break;
|
|
1383
|
-
case CLAUDE:
|
|
1384
|
-
resp = await promptClaude(input, {
|
|
1385
|
-
messages, model, ...options, attachments,
|
|
1386
|
-
});
|
|
1387
|
-
break;
|
|
1388
|
-
case OLLAMA:
|
|
1389
|
-
resp = await promptOllama(input, { messages, model, ...options });
|
|
1390
|
-
break;
|
|
1391
|
-
case AZURE:
|
|
1392
|
-
resp = await promptAzure(input, { messages, model, ...options });
|
|
1393
|
-
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}'.`);
|
|
1394
1390
|
}
|
|
1395
1391
|
chat.response = resp.text;
|
|
1396
|
-
chat
|
|
1392
|
+
chat.request && chat.response && session.messages.push(chat);
|
|
1397
1393
|
await setSession(sessionId, session, options);
|
|
1398
1394
|
return { sessionId, ...packResult(resp) };
|
|
1399
1395
|
};
|