goblin-agents 3.1.0 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/llm/aiAgent.js +68 -10
- package/lib/llm/providers/ollama.js +2 -2
- package/lib/llm/providers/opanAi.js +5 -5
- package/lib/llm/utils.js +22 -0
- package/package.json +1 -1
package/lib/llm/aiAgent.js
CHANGED
|
@@ -13,7 +13,12 @@ const {
|
|
|
13
13
|
} = require('xcraft-core-stones');
|
|
14
14
|
const {getMutex} = require('xcraft-core-utils/lib/locks.js');
|
|
15
15
|
const LLMProviders = require('./providers.js');
|
|
16
|
-
const {
|
|
16
|
+
const {
|
|
17
|
+
OCR_SYSTEM_PROMPT,
|
|
18
|
+
getPDFImages,
|
|
19
|
+
splitThinkTags,
|
|
20
|
+
getContextUsage,
|
|
21
|
+
} = require('./utils.js');
|
|
17
22
|
const Logger = require('../logger.js');
|
|
18
23
|
|
|
19
24
|
const CONVERSATION_TITLE_PROMPT = `Create a concise and informative title that summarizes the main topic of the conversation.
|
|
@@ -30,6 +35,15 @@ class MessageShape {
|
|
|
30
35
|
tool_name = option(string);
|
|
31
36
|
}
|
|
32
37
|
|
|
38
|
+
class UsageShape {
|
|
39
|
+
used = number;
|
|
40
|
+
reserved = number;
|
|
41
|
+
remaining = number;
|
|
42
|
+
total = number;
|
|
43
|
+
percentage = number;
|
|
44
|
+
nearLimit = boolean;
|
|
45
|
+
}
|
|
46
|
+
|
|
33
47
|
class OptionsShape {
|
|
34
48
|
// Active NUMA (Non-Uniform Memory Access) pour optimiser la gestion mémoire sur les architectures multi-sockets.
|
|
35
49
|
numa = option(boolean);
|
|
@@ -150,6 +164,7 @@ class AiAgentShape {
|
|
|
150
164
|
role = string; //agent role
|
|
151
165
|
prompt = string; //default system prompt
|
|
152
166
|
contexts = record(string, string); //additionnal system prompt context
|
|
167
|
+
usages = record(string, UsageShape);
|
|
153
168
|
reasonings = record(
|
|
154
169
|
string,
|
|
155
170
|
enumeration(false, true, 'low', 'medium', 'high')
|
|
@@ -204,6 +219,7 @@ class AiAgentLogic extends Elf.Archetype {
|
|
|
204
219
|
provider: defaultValues.provider,
|
|
205
220
|
model: defaultValues.model,
|
|
206
221
|
otherModels: defaultValues.otherModels,
|
|
222
|
+
usages: {},
|
|
207
223
|
messages: {},
|
|
208
224
|
contexts: {},
|
|
209
225
|
reasonings: {},
|
|
@@ -239,10 +255,21 @@ class AiAgentLogic extends Elf.Archetype {
|
|
|
239
255
|
this.patch(patch);
|
|
240
256
|
state.version = version;
|
|
241
257
|
}
|
|
258
|
+
_updateContextUsage(contextId, usage) {
|
|
259
|
+
this.state.usages[contextId] = usage;
|
|
260
|
+
}
|
|
242
261
|
|
|
243
262
|
reset(contextId) {
|
|
244
263
|
const {state} = this;
|
|
245
264
|
state.messages[contextId] = [];
|
|
265
|
+
state.usages[contextId] = {
|
|
266
|
+
used: 0,
|
|
267
|
+
reserved: 0,
|
|
268
|
+
remaining: this.state.options?.num_ctx || 4096,
|
|
269
|
+
total: this.state.options?.num_ctx || 4096,
|
|
270
|
+
percentage: 0,
|
|
271
|
+
nearLimit: false,
|
|
272
|
+
};
|
|
246
273
|
if (state.contexts[contextId]) {
|
|
247
274
|
delete state.contexts[contextId];
|
|
248
275
|
}
|
|
@@ -509,7 +536,7 @@ class AiAgent extends Elf {
|
|
|
509
536
|
],
|
|
510
537
|
},
|
|
511
538
|
];
|
|
512
|
-
const result = await this._provider.chat({
|
|
539
|
+
const {result} = await this._provider.chat({
|
|
513
540
|
model: this.state.model,
|
|
514
541
|
reasoning: this.state.reasoning,
|
|
515
542
|
messages,
|
|
@@ -542,7 +569,7 @@ class AiAgent extends Elf {
|
|
|
542
569
|
],
|
|
543
570
|
},
|
|
544
571
|
];
|
|
545
|
-
const result = await this._provider.chat({
|
|
572
|
+
const {result} = await this._provider.chat({
|
|
546
573
|
model: this.state.model,
|
|
547
574
|
reasoning: this.state.reasoning,
|
|
548
575
|
messages,
|
|
@@ -594,6 +621,37 @@ class AiAgent extends Elf {
|
|
|
594
621
|
this.logic.set(contextId, messages);
|
|
595
622
|
}
|
|
596
623
|
|
|
624
|
+
async getContextUsage(contextId) {
|
|
625
|
+
const usage = this.state.usages[contextId];
|
|
626
|
+
if (!usage) {
|
|
627
|
+
return {
|
|
628
|
+
used: 0,
|
|
629
|
+
reserved: 0,
|
|
630
|
+
remaining: this.state.options?.num_ctx || 4096,
|
|
631
|
+
total: this.state.options?.num_ctx || 4096,
|
|
632
|
+
percentage: 0,
|
|
633
|
+
nearLimit: false,
|
|
634
|
+
};
|
|
635
|
+
}
|
|
636
|
+
return {
|
|
637
|
+
used: usage.used,
|
|
638
|
+
reserved: usage.reserved,
|
|
639
|
+
remaining: usage.remaining,
|
|
640
|
+
total: usage.total,
|
|
641
|
+
percentage: usage.percentage,
|
|
642
|
+
nearLimit: usage.nearLimit,
|
|
643
|
+
};
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
async _updateContextUsage(contextId, response) {
|
|
647
|
+
const usage = getContextUsage(
|
|
648
|
+
response,
|
|
649
|
+
this.state.options?.num_ctx || undefined,
|
|
650
|
+
this.state.options?.num_predict || undefined
|
|
651
|
+
);
|
|
652
|
+
this.logic._updateContextUsage(contextId, usage);
|
|
653
|
+
}
|
|
654
|
+
|
|
597
655
|
/**
|
|
598
656
|
* Set chat session context, model and level of reasoning
|
|
599
657
|
* @param {string} contextId
|
|
@@ -623,7 +681,7 @@ class AiAgent extends Elf {
|
|
|
623
681
|
return {context, model, reasoning};
|
|
624
682
|
}
|
|
625
683
|
|
|
626
|
-
async chat(contextId, question, userDesktopId) {
|
|
684
|
+
async chat(contextId, question, userDesktopId, disableTool = false) {
|
|
627
685
|
const logger = Logger.get(contextId, this);
|
|
628
686
|
|
|
629
687
|
await this._mutex.lock(this.id);
|
|
@@ -670,11 +728,11 @@ ${context}
|
|
|
670
728
|
messages,
|
|
671
729
|
options,
|
|
672
730
|
format: this.state.format?.toJS(),
|
|
673
|
-
tools: this.state.tools?.toJS(),
|
|
731
|
+
tools: disableTool ? undefined : this.state.tools?.toJS(),
|
|
674
732
|
};
|
|
675
733
|
|
|
676
|
-
const result = await this._provider.chat(chatCompletionRequest);
|
|
677
|
-
|
|
734
|
+
const {result, usage} = await this._provider.chat(chatCompletionRequest);
|
|
735
|
+
await this._updateContextUsage(contextId, usage);
|
|
678
736
|
logger.push('aiAgent.chat', {
|
|
679
737
|
id: this.id,
|
|
680
738
|
model: chatCompletionRequest.model,
|
|
@@ -684,10 +742,10 @@ ${context}
|
|
|
684
742
|
),
|
|
685
743
|
question,
|
|
686
744
|
reasoning: result.message?.reasoning,
|
|
687
|
-
callTools: !!result.message.tool_calls?.length,
|
|
745
|
+
callTools: disableTool ? 'disabled' : !!result.message.tool_calls?.length,
|
|
688
746
|
});
|
|
689
747
|
|
|
690
|
-
if (this.state.toolServiceId && this.state.tools) {
|
|
748
|
+
if (this.state.toolServiceId && this.state.tools && !disableTool) {
|
|
691
749
|
const used = await this.callTools(
|
|
692
750
|
contextId,
|
|
693
751
|
result.message,
|
|
@@ -695,7 +753,7 @@ ${context}
|
|
|
695
753
|
);
|
|
696
754
|
if (used) {
|
|
697
755
|
//leave for a second turn
|
|
698
|
-
this.log.dbg(`🐈 Chat session ended : ${contextId}`);
|
|
756
|
+
this.log.dbg(`🐈 Chat session ended by a tool call: ${contextId}`);
|
|
699
757
|
return true;
|
|
700
758
|
}
|
|
701
759
|
}
|
|
@@ -53,7 +53,7 @@ class OpenAIProvider extends LLMProvider {
|
|
|
53
53
|
methods: ['POST'],
|
|
54
54
|
},
|
|
55
55
|
});
|
|
56
|
-
const {choices} = chat;
|
|
56
|
+
const {choices, usage} = chat;
|
|
57
57
|
if (chat.error) {
|
|
58
58
|
const err = new Error(
|
|
59
59
|
chat.error?.metadata
|
|
@@ -67,9 +67,9 @@ class OpenAIProvider extends LLMProvider {
|
|
|
67
67
|
choices[0].message.content = JSON.parse(
|
|
68
68
|
choices[0].message.content
|
|
69
69
|
).results;
|
|
70
|
-
return choices[0];
|
|
70
|
+
return {result: choices[0], usage};
|
|
71
71
|
} else {
|
|
72
|
-
return choices[0];
|
|
72
|
+
return {result: choices[0], usage};
|
|
73
73
|
}
|
|
74
74
|
}
|
|
75
75
|
|
|
@@ -96,8 +96,8 @@ class OpenAIProvider extends LLMProvider {
|
|
|
96
96
|
stream,
|
|
97
97
|
reasoning,
|
|
98
98
|
};
|
|
99
|
-
const
|
|
100
|
-
return
|
|
99
|
+
const {result} = await this.chat(chatRequest);
|
|
100
|
+
return result.message.content;
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
async embed(embedRequest) {
|
package/lib/llm/utils.js
CHANGED
|
@@ -379,6 +379,27 @@ function splitThinkTags(input) {
|
|
|
379
379
|
};
|
|
380
380
|
}
|
|
381
381
|
|
|
382
|
+
function getContextUsage(usage, numCtx = 65536, maxTokens = 4096) {
|
|
383
|
+
const used = usage?.prompt_tokens ?? usage?.prompt_eval_count;
|
|
384
|
+
|
|
385
|
+
if (!Number.isFinite(used)) {
|
|
386
|
+
return null;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
const available = Math.max(0, numCtx - used);
|
|
390
|
+
const reserved = Math.min(maxTokens, available);
|
|
391
|
+
const remaining = available - reserved;
|
|
392
|
+
|
|
393
|
+
return {
|
|
394
|
+
used,
|
|
395
|
+
reserved,
|
|
396
|
+
remaining,
|
|
397
|
+
total: numCtx,
|
|
398
|
+
percentage: Math.min(100, (used / numCtx) * 100),
|
|
399
|
+
nearLimit: used + maxTokens >= numCtx,
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
|
|
382
403
|
module.exports = {
|
|
383
404
|
chunkArray,
|
|
384
405
|
embedChunksInBatch,
|
|
@@ -394,4 +415,5 @@ module.exports = {
|
|
|
394
415
|
groupSentences,
|
|
395
416
|
buildPromptFromArticlesMulti,
|
|
396
417
|
splitThinkTags,
|
|
418
|
+
getContextUsage,
|
|
397
419
|
};
|