@sprucelabs/sprucebot-llm 17.0.0 → 18.0.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/README.md CHANGED
@@ -22,6 +22,9 @@ A TypeScript library for leveraging large language models to do... anything!
22
22
  * [Anthropic](#anthropic-adapter) - Claude models with prompt caching support
23
23
  * [Ollama](#ollama-adapter) - Run local models like Llama, Mistral, etc.
24
24
  * [Custom adapters](#custom-adapters) - Implement your own
25
+ * [Track token usage](#tracking-token-usage)
26
+ * Read cumulative input/output/total tokens from any adapter
27
+ * Includes Anthropic prompt-cache token counts
25
28
  * Fully typed
26
29
  * Built in modern TypeScript
27
30
  * Fully typed schema-based state management (powered by `@sprucelabs/schema`)
@@ -192,6 +195,7 @@ adapter.setReasoningEffort('low')
192
195
  - `adapter.setModel(model)`: set a default model for all requests unless a Skill overrides it.
193
196
  - `adapter.setMessageMemoryLimit(limit)`: limit how many tracked messages are sent to OpenAI.
194
197
  - `adapter.setReasoningEffort(effort)`: set `reasoning_effort` for models that support it.
198
+ - `adapter.getTokenUsage()`: returns cumulative [token usage](#tracking-token-usage) for this adapter. The OpenAI adapter currently returns zeros (not yet implemented).
195
199
  - `OpenAiAdapter.OpenAI`: assign a custom OpenAI client class (useful for tests).
196
200
 
197
201
  Requests are sent via `openai.chat.completions.create(...)` with messages built by the adapter from the Bot state and history.
@@ -234,6 +238,8 @@ Token usage (including cache creation and cache read tokens) is logged at the `i
234
238
  [TOKEN USAGE] input=1234 cache_create=800 cache_read=400 output=256
235
239
  ```
236
240
 
241
+ The same numbers are also available programmatically via [`adapter.getTokenUsage()`](#tracking-token-usage).
242
+
237
243
  No configuration is required — caching is applied automatically.
238
244
 
239
245
  ### Ollama adapter
@@ -270,6 +276,48 @@ await bot.sendMessage('Hello!')
270
276
 
271
277
  The Ollama adapter connects to `http://localhost:11434/v1` by default (Ollama's OpenAI-compatible endpoint).
272
278
 
279
+ ### Tracking token usage
280
+
281
+ Every adapter implements `getTokenUsage()`, which returns the **cumulative** token usage for that adapter instance since it was created:
282
+
283
+ ```ts
284
+ import { LmmTokenUsage } from '@sprucelabs/sprucebot-llm'
285
+
286
+ await bot.sendMessage('Hello!')
287
+
288
+ const usage: LmmTokenUsage = adapter.getTokenUsage()
289
+ // {
290
+ // inputTokens: 1234,
291
+ // outputTokens: 256,
292
+ // totalTokens: 1490,
293
+ // cacheCreationTokens: 800, // Anthropic only
294
+ // cacheReadTokens: 400, // Anthropic only
295
+ // }
296
+ ```
297
+
298
+ The `LmmTokenUsage` shape:
299
+
300
+ | Field | Type | Notes |
301
+ |-------|------|-------|
302
+ | `inputTokens` | `number` | Prompt / input tokens |
303
+ | `outputTokens` | `number` | Completion / output tokens |
304
+ | `totalTokens` | `number` | `inputTokens + outputTokens` |
305
+ | `cacheCreationTokens` | `number` (optional) | Anthropic only — tokens written to the prompt cache |
306
+ | `cacheReadTokens` | `number` (optional) | Anthropic only — tokens served from the prompt cache |
307
+
308
+ **Adapter support:**
309
+
310
+ | Adapter | `getTokenUsage()` |
311
+ |---------|-------------------|
312
+ | Anthropic | ✅ Fully tracked, including cache tokens |
313
+ | OpenAI | ⚠️ Returns zeros — not yet implemented |
314
+ | Ollama | ⚠️ Returns zeros — not yet implemented |
315
+
316
+ **Things to know:**
317
+
318
+ - **Cumulative, not per-call.** Totals accumulate across every `sendMessage` call on the adapter, and there is no reset method. To measure a single call, read `getTokenUsage()` before and after and diff the values.
319
+ - **Per adapter, not per bot.** Usage lives on the adapter instance. If you share one adapter across multiple bots (e.g. through a single `SprucebotLlmFactory`), the totals aggregate across all of them.
320
+
273
321
  ### Custom adapters
274
322
 
275
323
  You can bring your own adapter by implementing the `LlmAdapter` interface and passing it to `SprucebotLlmFactory.Factory(...)`:
@@ -277,6 +325,8 @@ You can bring your own adapter by implementing the `LlmAdapter` interface and pa
277
325
  ```ts
278
326
  import {
279
327
  LlmAdapter,
328
+ LllmReasoningEffort,
329
+ LmmTokenUsage,
280
330
  SprucebotLlmBot,
281
331
  SprucebotLlmFactory,
282
332
  } from '@sprucelabs/sprucebot-llm'
@@ -288,6 +338,15 @@ class MyAdapter implements LlmAdapter {
288
338
  // Send to your model and return the model response as a string
289
339
  return `echo: ${messages[messages.length - 1]?.message ?? ''}`
290
340
  }
341
+
342
+ setModel(_model: string) {}
343
+ setReasoningEffort(_effort: LllmReasoningEffort) {}
344
+ setMemoryLimit(_limit: number) {}
345
+
346
+ // Return cumulative token usage for this adapter (zeros if you don't track it)
347
+ getTokenUsage(): LmmTokenUsage {
348
+ return { inputTokens: 0, outputTokens: 0, totalTokens: 0 }
349
+ }
291
350
  }
292
351
 
293
352
  const bots = SprucebotLlmFactory.Factory(new MyAdapter())
@@ -10,7 +10,7 @@ export { default as SpyLlmAdapter } from './tests/SpyAdapter';
10
10
  export { default as SpyOpenAiApi } from './bots/adapters/SpyOpenAiModule';
11
11
  export { default as OllamaAdapter } from './bots/adapters/OllamaAdapter';
12
12
  export * from './bots/adapters/OllamaAdapter';
13
- export { default as AthropicAdapter } from './bots/adapters/AnthropicAdapter';
13
+ export { default as AnthropicAdapter } from './bots/adapters/AnthropicAdapter';
14
14
  export * from './bots/adapters/AnthropicAdapter';
15
15
  export { default as LlmAdapterLoader } from './bots/adapters/LlmAdapterLoader';
16
16
  export * from './bots/adapters/LlmAdapterLoader';
@@ -10,7 +10,7 @@ export { default as SpyLlmAdapter } from './tests/SpyAdapter.js';
10
10
  export { default as SpyOpenAiApi } from './bots/adapters/SpyOpenAiModule.js';
11
11
  export { default as OllamaAdapter } from './bots/adapters/OllamaAdapter.js';
12
12
  export * from './bots/adapters/OllamaAdapter.js';
13
- export { default as AthropicAdapter } from './bots/adapters/AnthropicAdapter.js';
13
+ export { default as AnthropicAdapter } from './bots/adapters/AnthropicAdapter.js';
14
14
  export * from './bots/adapters/AnthropicAdapter.js';
15
15
  export { default as LlmAdapterLoader } from './bots/adapters/LlmAdapterLoader.js';
16
16
  export * from './bots/adapters/LlmAdapterLoader.js';
package/build/index.d.ts CHANGED
@@ -10,7 +10,7 @@ export { default as SpyLlmAdapter } from './tests/SpyAdapter';
10
10
  export { default as SpyOpenAiApi } from './bots/adapters/SpyOpenAiModule';
11
11
  export { default as OllamaAdapter } from './bots/adapters/OllamaAdapter';
12
12
  export * from './bots/adapters/OllamaAdapter';
13
- export { default as AthropicAdapter } from './bots/adapters/AnthropicAdapter';
13
+ export { default as AnthropicAdapter } from './bots/adapters/AnthropicAdapter';
14
14
  export * from './bots/adapters/AnthropicAdapter';
15
15
  export { default as LlmAdapterLoader } from './bots/adapters/LlmAdapterLoader';
16
16
  export * from './bots/adapters/LlmAdapterLoader';
package/build/index.js CHANGED
@@ -17,7 +17,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
17
17
  return (mod && mod.__esModule) ? mod : { "default": mod };
18
18
  };
19
19
  Object.defineProperty(exports, "__esModule", { value: true });
20
- exports.MockAdapterLoader = exports.LlmAdapterLoader = exports.AthropicAdapter = exports.OllamaAdapter = exports.SpyOpenAiApi = exports.SpyLlmAdapter = exports.SpyLllmBot = exports.MockLlmSkill = exports.SprucebotLlmError = exports.OpenAiAdapter = exports.SprucebotLlmSkillImpl = exports.SprucebotLlmBotImpl = exports.SprucebotLlmFactory = void 0;
20
+ exports.MockAdapterLoader = exports.LlmAdapterLoader = exports.AnthropicAdapter = exports.OllamaAdapter = exports.SpyOpenAiApi = exports.SpyLlmAdapter = exports.SpyLllmBot = exports.MockLlmSkill = exports.SprucebotLlmError = exports.OpenAiAdapter = exports.SprucebotLlmSkillImpl = exports.SprucebotLlmBotImpl = exports.SprucebotLlmFactory = void 0;
21
21
  var SprucebotLlmFactory_1 = require("./bots/SprucebotLlmFactory");
22
22
  Object.defineProperty(exports, "SprucebotLlmFactory", { enumerable: true, get: function () { return __importDefault(SprucebotLlmFactory_1).default; } });
23
23
  var SprucebotLlmBotImpl_1 = require("./bots/SprucebotLlmBotImpl");
@@ -41,7 +41,7 @@ var OllamaAdapter_1 = require("./bots/adapters/OllamaAdapter");
41
41
  Object.defineProperty(exports, "OllamaAdapter", { enumerable: true, get: function () { return __importDefault(OllamaAdapter_1).default; } });
42
42
  __exportStar(require("./bots/adapters/OllamaAdapter"), exports);
43
43
  var AnthropicAdapter_1 = require("./bots/adapters/AnthropicAdapter");
44
- Object.defineProperty(exports, "AthropicAdapter", { enumerable: true, get: function () { return __importDefault(AnthropicAdapter_1).default; } });
44
+ Object.defineProperty(exports, "AnthropicAdapter", { enumerable: true, get: function () { return __importDefault(AnthropicAdapter_1).default; } });
45
45
  __exportStar(require("./bots/adapters/AnthropicAdapter"), exports);
46
46
  var LlmAdapterLoader_1 = require("./bots/adapters/LlmAdapterLoader");
47
47
  Object.defineProperty(exports, "LlmAdapterLoader", { enumerable: true, get: function () { return __importDefault(LlmAdapterLoader_1).default; } });
package/package.json CHANGED
@@ -8,7 +8,7 @@
8
8
  "eta"
9
9
  ]
10
10
  },
11
- "version": "17.0.0",
11
+ "version": "18.0.0",
12
12
  "files": [
13
13
  "build"
14
14
  ],