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.
@@ -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 {OCR_SYSTEM_PROMPT, getPDFImages, splitThinkTags} = require('./utils.js');
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
  }
@@ -37,9 +37,9 @@ class OllamaProvider extends LLMProvider {
37
37
  });
38
38
  if (useFormat) {
39
39
  chat.message.content = JSON.parse(chat.message.content);
40
- return chat;
40
+ return {result: chat};
41
41
  } else {
42
- return chat;
42
+ return {result: chat};
43
43
  }
44
44
  }
45
45
 
@@ -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 chat = await this.chat(chatRequest);
100
- return chat.message.content;
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
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "goblin-agents",
3
- "version": "3.1.0",
3
+ "version": "3.2.0",
4
4
  "description": "AI Agents framework",
5
5
  "author": "Epsitec SA",
6
6
  "contributors": [