utilitas 1998.2.23 → 1998.2.24
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/README.md +1 -1
- package/dist/utilitas.lite.mjs +1 -1
- package/dist/utilitas.lite.mjs.map +1 -1
- package/lib/alan.mjs +165 -45
- package/lib/manifest.mjs +1 -1
- package/lib/utilitas.mjs +3 -3
- package/package.json +1 -1
package/lib/alan.mjs
CHANGED
|
@@ -7,9 +7,12 @@ import { create as createUoid } from './uoid.mjs';
|
|
|
7
7
|
import {
|
|
8
8
|
log as _log,
|
|
9
9
|
renderText as _renderText,
|
|
10
|
-
base64Encode, ensureArray, ensureString, extract,
|
|
10
|
+
base64Encode, ensureArray, ensureString, extract,
|
|
11
|
+
ignoreErrFunc,
|
|
12
|
+
insensitiveCompare,
|
|
13
|
+
isSet,
|
|
11
14
|
need, parseJson,
|
|
12
|
-
throwError
|
|
15
|
+
throwError
|
|
13
16
|
} from './utilitas.mjs';
|
|
14
17
|
|
|
15
18
|
const _NEED = [
|
|
@@ -82,6 +85,7 @@ const log = (cnt, opt) => _log(cnt, import.meta.url, { time: 1, ...opt || {} });
|
|
|
82
85
|
const CONTENT_IS_REQUIRED = 'Content is required.';
|
|
83
86
|
const assertContent = content => assert(content.length, CONTENT_IS_REQUIRED);
|
|
84
87
|
|
|
88
|
+
|
|
85
89
|
const DEFAULT_MODELS = {
|
|
86
90
|
[CHATGPT_MINI]: GPT_4O_MINI,
|
|
87
91
|
[CHATGPT_REASONING]: GPT_O3_MINI,
|
|
@@ -123,7 +127,7 @@ const MODELS = {
|
|
|
123
127
|
trainingData: 'Oct 2023',
|
|
124
128
|
json: true,
|
|
125
129
|
vision: true,
|
|
126
|
-
|
|
130
|
+
tools: true,
|
|
127
131
|
audio: 'gpt-4o-mini-audio-preview',
|
|
128
132
|
supportedMimeTypes: [
|
|
129
133
|
png, jpeg, gif, webp,
|
|
@@ -142,7 +146,7 @@ const MODELS = {
|
|
|
142
146
|
trainingData: 'Oct 2023',
|
|
143
147
|
json: true,
|
|
144
148
|
vision: true,
|
|
145
|
-
|
|
149
|
+
tools: true,
|
|
146
150
|
audio: 'gpt-4o-audio-preview',
|
|
147
151
|
supportedMimeTypes: [
|
|
148
152
|
png, jpeg, gif, webp,
|
|
@@ -162,6 +166,7 @@ const MODELS = {
|
|
|
162
166
|
json: true,
|
|
163
167
|
reasoning: true,
|
|
164
168
|
vision: true,
|
|
169
|
+
tools: true,
|
|
165
170
|
// audio: 'gpt-4o-audio-preview', // fallback to GPT-4O to support audio
|
|
166
171
|
supportedMimeTypes: [
|
|
167
172
|
png, jpeg, gif, webp,
|
|
@@ -181,6 +186,7 @@ const MODELS = {
|
|
|
181
186
|
json: true,
|
|
182
187
|
reasoning: true,
|
|
183
188
|
vision: true,
|
|
189
|
+
tools: true,
|
|
184
190
|
// audio: 'gpt-4o-mini-audio-preview', // fallback to GPT-4O-MINI to support audio
|
|
185
191
|
supportedMimeTypes: [
|
|
186
192
|
png, jpeg, gif, webp,
|
|
@@ -300,6 +306,7 @@ const MODELS = {
|
|
|
300
306
|
tokenLimitsITPM: 40000,
|
|
301
307
|
tokenLimitsOTPM: 8000,
|
|
302
308
|
trainingData: 'Apr 2024',
|
|
309
|
+
tools: true,
|
|
303
310
|
supportedMimeTypes: [
|
|
304
311
|
png, jpeg, gif, webp, pdf,
|
|
305
312
|
],
|
|
@@ -319,6 +326,7 @@ const MODELS = {
|
|
|
319
326
|
tokenLimitsOTPM: 8000,
|
|
320
327
|
trainingData: 'Apr 2024', // ?
|
|
321
328
|
reasoning: true,
|
|
329
|
+
tools: true,
|
|
322
330
|
supportedMimeTypes: [
|
|
323
331
|
png, jpeg, gif, webp, pdf,
|
|
324
332
|
],
|
|
@@ -451,6 +459,35 @@ const countTokens = async (input, options) => {
|
|
|
451
459
|
);
|
|
452
460
|
};
|
|
453
461
|
|
|
462
|
+
const tools = [
|
|
463
|
+
{
|
|
464
|
+
def: {
|
|
465
|
+
type: 'function', strict: true, function: {
|
|
466
|
+
name: 'testFunctionCall',
|
|
467
|
+
description: 'This is a test function call',
|
|
468
|
+
parameters: {
|
|
469
|
+
type: 'object',
|
|
470
|
+
properties: {
|
|
471
|
+
a: { type: 'string', description: 'Please create a random string' },
|
|
472
|
+
b: { type: 'string', enum: ['A', 'B'], description: 'Enum parameter' }
|
|
473
|
+
},
|
|
474
|
+
required: ['a'],
|
|
475
|
+
additionalProperties: false
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
},
|
|
479
|
+
func: async args => 'OK',
|
|
480
|
+
},
|
|
481
|
+
];
|
|
482
|
+
|
|
483
|
+
const toolsClaude = tools.map(x => ({
|
|
484
|
+
...x, def: {
|
|
485
|
+
name: x.def.function.name,
|
|
486
|
+
description: x.def.function.description,
|
|
487
|
+
input_schema: x.def.function.parameters,
|
|
488
|
+
}
|
|
489
|
+
}));
|
|
490
|
+
|
|
454
491
|
const selectGptAudioModel = options => {
|
|
455
492
|
assert(
|
|
456
493
|
MODELS[options.model]?.audio,
|
|
@@ -630,12 +667,12 @@ const packResp = async (resp, options) => {
|
|
|
630
667
|
};
|
|
631
668
|
|
|
632
669
|
const packGptResp = async (resp, options) => {
|
|
633
|
-
const text = resp?.choices?.[0]?.message?.content
|
|
634
|
-
|| resp?.choices?.[0]?.message?.audio?.transcript
|
|
635
|
-
|| resp?.text?.()
|
|
636
|
-
|| resp?.content?.text
|
|
637
|
-
|| resp?.message?.content || '';
|
|
638
|
-
const audio = resp?.choices?.[0]?.message?.audio?.data;
|
|
670
|
+
const text = resp?.choices?.[0]?.message?.content // ChatGPT
|
|
671
|
+
|| resp?.choices?.[0]?.message?.audio?.transcript // ChatGPT audio mode
|
|
672
|
+
|| resp?.text?.() // Gemini
|
|
673
|
+
|| resp?.content?.find(x => x.type === 'text')?.text // Claude
|
|
674
|
+
|| resp?.message?.content || ''; // Ollama
|
|
675
|
+
const audio = resp?.choices?.[0]?.message?.audio?.data; // ChatGPT audio mode
|
|
639
676
|
if (options?.raw) { return resp; }
|
|
640
677
|
else if (options?.simple && options?.jsonMode) { return parseJson(text); }
|
|
641
678
|
else if (options?.simple && options?.audioMode) { return audio; }
|
|
@@ -645,6 +682,47 @@ const packGptResp = async (resp, options) => {
|
|
|
645
682
|
return await packResp({ text, audio, references: resp?.references }, options);
|
|
646
683
|
};
|
|
647
684
|
|
|
685
|
+
const handleToolsCall = async (msg, options) => {
|
|
686
|
+
let content = [], preRes = [], input, packMsg;
|
|
687
|
+
if (msg?.tool_calls?.length) {
|
|
688
|
+
switch (options?.flavor) {
|
|
689
|
+
case CLAUDE: preRes.push({ role: 'assistant', content: msg?.tool_calls }); break;
|
|
690
|
+
case CHATGPT: default: preRes.push({ role: 'assistant', ...msg });
|
|
691
|
+
}
|
|
692
|
+
for (const fn of msg.tool_calls) {
|
|
693
|
+
input = parseJson(fn?.function?.arguments || fn?.input);
|
|
694
|
+
switch (options?.flavor) {
|
|
695
|
+
case CLAUDE:
|
|
696
|
+
fn.input = input;
|
|
697
|
+
packMsg = (content, is_error) => ({
|
|
698
|
+
type: 'tool_result', tool_use_id: fn.id, content, is_error,
|
|
699
|
+
}); break;
|
|
700
|
+
case CHATGPT: default:
|
|
701
|
+
packMsg = (t, e) => ({
|
|
702
|
+
role: 'tool', tool_call_id: fn.id, [e ? 'error' : 'content']: t
|
|
703
|
+
});
|
|
704
|
+
}
|
|
705
|
+
const name = fn?.function?.name || fn?.name;
|
|
706
|
+
const func = tools.find(x => insensitiveCompare(
|
|
707
|
+
x.def?.function?.name || x?.def?.name, name
|
|
708
|
+
))?.func;
|
|
709
|
+
if (!func) {
|
|
710
|
+
content.push(packMsg(`Function call failed, invalid function name: ${name}`, true));
|
|
711
|
+
continue;
|
|
712
|
+
}
|
|
713
|
+
try {
|
|
714
|
+
content.push(packMsg(await func(...Object.values(input))));
|
|
715
|
+
} catch (err) {
|
|
716
|
+
content.push(packMsg(`Function call failed: ${err.message}`, true));
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
switch (options?.flavor) {
|
|
720
|
+
case CLAUDE: content = [{ role: 'user', content }];
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
return [...preRes, ...content];
|
|
724
|
+
};
|
|
725
|
+
|
|
648
726
|
const promptChatGPT = async (content, options = {}) => {
|
|
649
727
|
const { client } = await getOpenAIClient(options);
|
|
650
728
|
// https://github.com/openai/openai-node?tab=readme-ov-file#streaming-responses
|
|
@@ -674,45 +752,65 @@ const promptChatGPT = async (content, options = {}) => {
|
|
|
674
752
|
let format;
|
|
675
753
|
[format, options.audioMimeType, options.suffix]
|
|
676
754
|
= options?.stream ? ['pcm16', pcm16, 'pcm.wav'] : [WAV, wav, WAV];
|
|
677
|
-
let [resp, resultText, resultAudio, chunk] = [
|
|
755
|
+
let [resp, resultText, resultAudio, chunk, resultTools] = [
|
|
678
756
|
await client.chat.completions.create({
|
|
679
757
|
modalities, audio: options?.audio || (
|
|
680
758
|
modalities?.find?.(x => x === AUDIO) && {
|
|
681
759
|
voice: DEFAULT_MODELS[OPENAI_VOICE], format
|
|
682
760
|
}
|
|
683
|
-
), ...messages([
|
|
684
|
-
|
|
761
|
+
), ...messages([
|
|
762
|
+
...options?.messages || [], message,
|
|
763
|
+
...options?.toolsResult || [],
|
|
764
|
+
]), ...MODELS[options.model]?.tools ? {
|
|
765
|
+
tools: options?.tools ?? tools.map(x => x.def),
|
|
766
|
+
} : {}, ...options?.jsonMode ? {
|
|
685
767
|
response_format: { type: JSON_OBJECT }
|
|
686
|
-
} : {}, model: options.model, stream: !!options?.stream,
|
|
687
|
-
}), '', Buffer.alloc(0), null
|
|
768
|
+
} : {}, model: options.model, stream: !!options?.stream, store: true,
|
|
769
|
+
}), '', Buffer.alloc(0), null, [],
|
|
688
770
|
];
|
|
689
|
-
if (
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
chunk.choices[0]
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
771
|
+
if (options?.stream) {
|
|
772
|
+
for await (chunk of resp) {
|
|
773
|
+
const deltaText = chunk.choices[0]?.delta?.content
|
|
774
|
+
|| chunk.choices[0]?.delta?.audio?.transcript || '';
|
|
775
|
+
const deltaAudio = chunk.choices[0]?.delta?.audio?.data ? await convert(
|
|
776
|
+
chunk.choices[0].delta.audio.data, { input: BASE64, expected: BUFFER }
|
|
777
|
+
) : Buffer.alloc(0);
|
|
778
|
+
const deltaFunc = chunk.choices[0]?.delta?.tool_calls || [];
|
|
779
|
+
for (const x in deltaFunc) {
|
|
780
|
+
let curFunc = resultTools.find(z => z.index === deltaFunc[x].index);
|
|
781
|
+
curFunc || (resultTools.push(curFunc = {}));
|
|
782
|
+
isSet(deltaFunc[x].index, true) && (curFunc.index = deltaFunc[x].index);
|
|
783
|
+
deltaFunc[x].id && (curFunc.id = deltaFunc[x].id);
|
|
784
|
+
deltaFunc[x].type && (curFunc.type = deltaFunc[x].type);
|
|
785
|
+
curFunc.function || (curFunc.function = { name: '', arguments: '' });
|
|
786
|
+
if (deltaFunc[x].function) {
|
|
787
|
+
deltaFunc[x].function.name && (curFunc.function.name += deltaFunc[x].function.name);
|
|
788
|
+
deltaFunc[x].function.arguments && (curFunc.function.arguments += deltaFunc[x].function.arguments);
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
if (deltaText === '' && !deltaAudio.length) { continue; }
|
|
792
|
+
resultText += deltaText;
|
|
793
|
+
resultAudio = Buffer.concat([resultAudio, deltaAudio]);
|
|
794
|
+
const respAudio = options?.delta ? deltaAudio : resultAudio;
|
|
795
|
+
chunk.choices[0].message = {
|
|
796
|
+
content: options?.delta ? deltaText : resultText,
|
|
797
|
+
...respAudio.length ? { audio: { data: respAudio } } : {},
|
|
798
|
+
};
|
|
799
|
+
await ignoreErrFunc(async () => await options?.stream?.(
|
|
800
|
+
await packGptResp(chunk, { ...options || {}, processing: true })
|
|
801
|
+
), LOG);
|
|
802
|
+
}
|
|
803
|
+
chunk.choices?.[0] || (chunk.choices = [{}]); // handle empty choices for Azure APIs
|
|
702
804
|
chunk.choices[0].message = {
|
|
703
|
-
content:
|
|
704
|
-
...
|
|
805
|
+
content: resultText, tool_calls: resultTools,
|
|
806
|
+
...resultAudio.length ? { audio: { data: resultAudio } } : {},
|
|
705
807
|
};
|
|
706
|
-
|
|
707
|
-
await packGptResp(chunk, { ...options || {}, processing: true })
|
|
708
|
-
), LOG);
|
|
808
|
+
resp = chunk;
|
|
709
809
|
}
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
content
|
|
713
|
-
|
|
714
|
-
};
|
|
715
|
-
return await packGptResp(chunk, options);
|
|
810
|
+
const toolsResult = await handleToolsCall(resp?.choices?.[0]?.message);
|
|
811
|
+
return await (toolsResult.length ? promptChatGPT(
|
|
812
|
+
content, { ...options || {}, toolsResult }
|
|
813
|
+
) : packGptResp(resp, options));
|
|
716
814
|
};
|
|
717
815
|
|
|
718
816
|
const promptAzure = async (content, options = {}) => await promptChatGPT(
|
|
@@ -749,33 +847,55 @@ const promptClaude = async (content, options = {}) => {
|
|
|
749
847
|
const resp = await client.messages.create({
|
|
750
848
|
model: options.model, max_tokens: MODELS[options.model].maxOutputTokens,
|
|
751
849
|
messages: [
|
|
752
|
-
...options?.messages || [], buildClaudeMessage(content, options)
|
|
850
|
+
...options?.messages || [], buildClaudeMessage(content, options), ...options?.toolsResult || [],
|
|
753
851
|
], stream: !!options?.stream, ...reasoning ? {
|
|
754
852
|
thinking: options?.thinking || { type: 'enabled', budget_tokens: 1024 },
|
|
755
|
-
} : {} // https://docs.anthropic.com/en/docs/build-with-claude/extended-thinking
|
|
853
|
+
} : {}, // https://docs.anthropic.com/en/docs/build-with-claude/extended-thinking
|
|
854
|
+
...MODELS[options.model]?.tools ? {
|
|
855
|
+
tools: options?.tools ?? toolsClaude.map(x => x.def),
|
|
856
|
+
} : {},
|
|
756
857
|
});
|
|
757
|
-
let [event,
|
|
858
|
+
let [event, txtResult, thinking, signature, result, thinkEnd, tool_calls]
|
|
859
|
+
= [null, '', '', '', '', '', []];
|
|
758
860
|
if (options?.stream) {
|
|
759
861
|
for await (event of resp) {
|
|
862
|
+
print(event);
|
|
760
863
|
let [thkDelta, txtDelta] = [
|
|
761
864
|
event?.content_block?.thinking || event?.delta?.thinking || '',
|
|
762
865
|
event?.content_block?.text || event?.delta?.text || '',
|
|
763
866
|
];
|
|
867
|
+
txtResult += txtDelta;
|
|
868
|
+
thinking += thkDelta;
|
|
869
|
+
signature = signature || event?.content_block?.signature || event?.delta?.signature || '';
|
|
764
870
|
if (reasoning) {
|
|
765
871
|
!result && thkDelta && (thkDelta = `${THINK_STR}\n${thkDelta}`);
|
|
766
872
|
result && txtDelta && !thinkEnd && (thinkEnd = thkDelta = `${thkDelta}\n${THINK_END}\n\n`);
|
|
767
873
|
}
|
|
874
|
+
if (event?.content_block?.type === 'tool_use') {
|
|
875
|
+
tool_calls.push({ ...event?.content_block, input: '' });
|
|
876
|
+
} else if (event?.delta?.partial_json) {
|
|
877
|
+
tool_calls[tool_calls.length - 1].input += event?.delta?.partial_json;
|
|
878
|
+
}
|
|
768
879
|
const delta = thkDelta + txtDelta;
|
|
769
880
|
if (delta === '') { continue; }
|
|
770
881
|
result += delta;
|
|
771
|
-
event.content = { text: options?.delta ? delta : result };
|
|
882
|
+
event.content = [{ type: 'text', text: options?.delta ? delta : result }];
|
|
772
883
|
await ignoreErrFunc(async () => await options.stream(
|
|
773
884
|
await packGptResp(event, { ...options || {}, processing: true })
|
|
774
885
|
), LOG);
|
|
775
886
|
}
|
|
776
|
-
event.content = { text: result };
|
|
887
|
+
event.content = [{ type: 'text', text: tool_calls.length ? txtResult : result }];
|
|
888
|
+
tool_calls.length && thinking && event.content.unshift({ type: 'thinking', thinking, signature });
|
|
889
|
+
} else {
|
|
890
|
+
event = resp;
|
|
891
|
+
tool_calls = resp?.content?.filter?.(x => x.type === 'tool_use') || [];
|
|
892
|
+
}
|
|
893
|
+
const toolsResult = await handleToolsCall({ tool_calls }, { flavor: CLAUDE });
|
|
894
|
+
if (toolsResult.length) {
|
|
895
|
+
toolsResult[0].content.unshift(...event?.content.filter(x => x?.type !== 'tool_use'));
|
|
896
|
+
return await promptClaude(content, { ...options || {}, toolsResult });
|
|
777
897
|
}
|
|
778
|
-
return
|
|
898
|
+
return packGptResp(event, options);
|
|
779
899
|
};
|
|
780
900
|
|
|
781
901
|
const uploadFile = async (input, options) => {
|
package/lib/manifest.mjs
CHANGED
package/lib/utilitas.mjs
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { assertPath, decodeBase64DataURL, readJson } from './storage.mjs';
|
|
2
|
-
import { basename as _basename, dirname, join, sep } from 'path';
|
|
3
1
|
import { fileURLToPath } from 'node:url';
|
|
2
|
+
import { basename as _basename, dirname, join, sep } from 'path';
|
|
4
3
|
import { promisify } from 'util';
|
|
5
4
|
import { validate as verifyUuid } from 'uuid';
|
|
6
5
|
import * as boxes from './boxes.mjs';
|
|
7
6
|
import color from './color.mjs';
|
|
7
|
+
import { assertPath, decodeBase64DataURL, readJson } from './storage.mjs';
|
|
8
8
|
|
|
9
9
|
const call = (f, ...a) => promisify(Array.isArray(f) ? f[0].bind(f[1]) : f)(...a);
|
|
10
10
|
const invalidTime = 'Invalid time.';
|
|
@@ -923,5 +923,5 @@ export {
|
|
|
923
923
|
verifyUrl,
|
|
924
924
|
verifyUuid,
|
|
925
925
|
voidFunc,
|
|
926
|
-
which
|
|
926
|
+
which
|
|
927
927
|
};
|