@sisu-ai/mw-usage-tracker 9.0.1 → 9.0.3
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 +81 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +107 -48
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
<div align="right">
|
|
2
|
+
<a href="https://github.com/finger-gun/sisu"><img src="https://github.com/finger-gun/sisu/raw/main/sisu-light.svg" alt="ProjectSpecs" width="100" /></a>
|
|
3
|
+
</div>
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
1
7
|
# @sisu-ai/mw-usage-tracker
|
|
2
8
|
|
|
3
9
|
Track token usage across your pipeline and estimate cost.
|
|
@@ -62,3 +68,78 @@ Discover what you can do through examples or documentation. Check it out at http
|
|
|
62
68
|
- [License](https://github.com/finger-gun/sisu/blob/main/LICENSE)
|
|
63
69
|
- [Report a Bug](https://github.com/finger-gun/sisu/issues/new?template=bug_report.md)
|
|
64
70
|
- [Request a Feature](https://github.com/finger-gun/sisu/issues/new?template=feature_request.md)
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## Documentation
|
|
75
|
+
|
|
76
|
+
**Core** — [Package docs](packages/core/README.md) · [Error types](packages/core/ERROR_TYPES.md)
|
|
77
|
+
|
|
78
|
+
**Adapters** — [OpenAI](packages/adapters/openai/README.md) · [Anthropic](packages/adapters/anthropic/README.md) · [Ollama](packages/adapters/ollama/README.md)
|
|
79
|
+
|
|
80
|
+
<details>
|
|
81
|
+
<summary>All middleware packages</summary>
|
|
82
|
+
|
|
83
|
+
- [@sisu-ai/mw-agent-run-api](packages/middleware/agent-run-api/README.md)
|
|
84
|
+
- [@sisu-ai/mw-context-compressor](packages/middleware/context-compressor/README.md)
|
|
85
|
+
- [@sisu-ai/mw-control-flow](packages/middleware/control-flow/README.md)
|
|
86
|
+
- [@sisu-ai/mw-conversation-buffer](packages/middleware/conversation-buffer/README.md)
|
|
87
|
+
- [@sisu-ai/mw-cors](packages/middleware/cors/README.md)
|
|
88
|
+
- [@sisu-ai/mw-error-boundary](packages/middleware/error-boundary/README.md)
|
|
89
|
+
- [@sisu-ai/mw-guardrails](packages/middleware/guardrails/README.md)
|
|
90
|
+
- [@sisu-ai/mw-invariants](packages/middleware/invariants/README.md)
|
|
91
|
+
- [@sisu-ai/mw-orchestration](packages/middleware/orchestration/README.md)
|
|
92
|
+
- [@sisu-ai/mw-rag](packages/middleware/rag/README.md)
|
|
93
|
+
- [@sisu-ai/mw-react-parser](packages/middleware/react-parser/README.md)
|
|
94
|
+
- [@sisu-ai/mw-register-tools](packages/middleware/register-tools/README.md)
|
|
95
|
+
- [@sisu-ai/mw-tool-calling](packages/middleware/tool-calling/README.md)
|
|
96
|
+
- [@sisu-ai/mw-trace-viewer](packages/middleware/trace-viewer/README.md)
|
|
97
|
+
- [@sisu-ai/mw-usage-tracker](packages/middleware/usage-tracker/README.md)
|
|
98
|
+
</details>
|
|
99
|
+
|
|
100
|
+
<details>
|
|
101
|
+
<summary>All tool packages</summary>
|
|
102
|
+
|
|
103
|
+
- [@sisu-ai/tool-aws-s3](packages/tools/aws-s3/README.md)
|
|
104
|
+
- [@sisu-ai/tool-azure-blob](packages/tools/azure-blob/README.md)
|
|
105
|
+
- [@sisu-ai/tool-extract-urls](packages/tools/extract-urls/README.md)
|
|
106
|
+
- [@sisu-ai/tool-github-projects](packages/tools/github-projects/README.md)
|
|
107
|
+
- [@sisu-ai/tool-summarize-text](packages/tools/summarize-text/README.md)
|
|
108
|
+
- [@sisu-ai/tool-terminal](packages/tools/terminal/README.md)
|
|
109
|
+
- [@sisu-ai/tool-vec-chroma](packages/tools/vec-chroma/README.md)
|
|
110
|
+
- [@sisu-ai/tool-web-fetch](packages/tools/web-fetch/README.md)
|
|
111
|
+
- [@sisu-ai/tool-web-search-duckduckgo](packages/tools/web-search-duckduckgo/README.md)
|
|
112
|
+
- [@sisu-ai/tool-web-search-google](packages/tools/web-search-google/README.md)
|
|
113
|
+
- [@sisu-ai/tool-web-search-openai](packages/tools/web-search-openai/README.md)
|
|
114
|
+
- [@sisu-ai/tool-wikipedia](packages/tools/wikipedia/README.md)
|
|
115
|
+
</details>
|
|
116
|
+
|
|
117
|
+
<details>
|
|
118
|
+
<summary>All examples</summary>
|
|
119
|
+
|
|
120
|
+
**Anthropic** — [hello](examples/anthropic-hello/README.md) · [control-flow](examples/anthropic-control-flow/README.md) · [stream](examples/anthropic-stream/README.md) · [weather](examples/anthropic-weather/README.md)
|
|
121
|
+
|
|
122
|
+
**Ollama** — [hello](examples/ollama-hello/README.md) · [stream](examples/ollama-stream/README.md) · [vision](examples/ollama-vision/README.md) · [weather](examples/ollama-weather/README.md) · [web-search](examples/ollama-web-search/README.md)
|
|
123
|
+
|
|
124
|
+
**OpenAI** — [hello](examples/openai-hello/README.md) · [weather](examples/openai-weather/README.md) · [stream](examples/openai-stream/README.md) · [vision](examples/openai-vision/README.md) · [reasoning](examples/openai-reasoning/README.md) · [react](examples/openai-react/README.md) · [control-flow](examples/openai-control-flow/README.md) · [branch](examples/openai-branch/README.md) · [parallel](examples/openai-parallel/README.md) · [graph](examples/openai-graph/README.md) · [orchestration](examples/openai-orchestration/README.md) · [orchestration-adaptive](examples/openai-orchestration-adaptive/README.md) · [guardrails](examples/openai-guardrails/README.md) · [error-handling](examples/openai-error-handling/README.md) · [rag-chroma](examples/openai-rag-chroma/README.md) · [web-search](examples/openai-web-search/README.md) · [web-fetch](examples/openai-web-fetch/README.md) · [wikipedia](examples/openai-wikipedia/README.md) · [terminal](examples/openai-terminal/README.md) · [github-projects](examples/openai-github-projects/README.md) · [server](examples/openai-server/README.md) · [aws-s3](examples/openai-aws-s3/README.md) · [azure-blob](examples/openai-azure-blob/README.md)
|
|
125
|
+
</details>
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## Contributing
|
|
130
|
+
|
|
131
|
+
We build Sisu in the open. Contributions welcome.
|
|
132
|
+
|
|
133
|
+
[Contributing Guide](CONTRIBUTING.md) · [Report a Bug](https://github.com/finger-gun/sisu/issues/new?template=bug_report.md) · [Request a Feature](https://github.com/finger-gun/sisu/issues/new?template=feature_request.md) · [Code of Conduct](CODE_OF_CONDUCT.md)
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
<div align="center">
|
|
138
|
+
|
|
139
|
+
**[Star on GitHub](https://github.com/finger-gun/sisu)** if Sisu helps you build better agents.
|
|
140
|
+
|
|
141
|
+
*Quiet, determined, relentlessly useful.*
|
|
142
|
+
|
|
143
|
+
[Apache 2.0 License](LICENSE)
|
|
144
|
+
|
|
145
|
+
</div>
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -2,36 +2,39 @@ export function usageTracker(prices, opts = {}) {
|
|
|
2
2
|
return async (ctx, next) => {
|
|
3
3
|
// Wrap model.generate to intercept responses
|
|
4
4
|
const orig = ctx.model.generate.bind(ctx.model);
|
|
5
|
-
const price = prices[ctx.model.name] ?? prices[
|
|
5
|
+
const price = prices[ctx.model.name] ?? prices["*"];
|
|
6
|
+
const state = ctx.state;
|
|
6
7
|
const totals = {
|
|
7
|
-
promptTokens: Number(
|
|
8
|
-
completionTokens: Number(
|
|
9
|
-
totalTokens: Number(
|
|
10
|
-
costUSD: Number(
|
|
8
|
+
promptTokens: Number(state.usage?.promptTokens ?? 0),
|
|
9
|
+
completionTokens: Number(state.usage?.completionTokens ?? 0),
|
|
10
|
+
totalTokens: Number(state.usage?.totalTokens ?? 0),
|
|
11
|
+
costUSD: Number(state.usage?.costUSD ?? 0),
|
|
11
12
|
};
|
|
12
|
-
|
|
13
|
-
// Inspect request messages to estimate image inputs
|
|
14
|
-
const reqMessages = args?.[0];
|
|
15
|
-
const imageCount = countImageInputs(reqMessages);
|
|
16
|
-
const imageTokens = imageCount * Number(price?.imageTokenPerImage ?? 1000);
|
|
17
|
-
const out = await orig(...args);
|
|
13
|
+
function applyUsage(out, imageCount, imageTokens) {
|
|
18
14
|
const u = out.usage;
|
|
19
15
|
if (u) {
|
|
20
16
|
const p = Number(u.promptTokens ?? 0);
|
|
21
17
|
const c = Number(u.completionTokens ?? 0);
|
|
22
|
-
const t = Number(u.totalTokens ??
|
|
18
|
+
const t = Number(u.totalTokens ?? p + c);
|
|
23
19
|
totals.promptTokens += p;
|
|
24
20
|
totals.completionTokens += c;
|
|
25
21
|
totals.totalTokens += t;
|
|
26
22
|
if (price) {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
23
|
+
const inPer1K = price.inputPer1K ??
|
|
24
|
+
(price.inputPer1M != null ? price.inputPer1M / 1000 : undefined) ??
|
|
25
|
+
0;
|
|
26
|
+
const outPer1K = price.outputPer1K ??
|
|
27
|
+
(price.outputPer1M != null
|
|
28
|
+
? price.outputPer1M / 1000
|
|
29
|
+
: undefined) ??
|
|
30
|
+
0;
|
|
31
31
|
const textPromptTokens = Math.max(0, p - (price.imageInputPer1K ? imageTokens : 0));
|
|
32
32
|
const tokenCost = (textPromptTokens / 1000) * inPer1K + (c / 1000) * outPer1K;
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
const perImage = price.imagePerImage != null
|
|
34
|
+
? price.imagePerImage
|
|
35
|
+
: price.imagePer1K != null
|
|
36
|
+
? price.imagePer1K / 1000
|
|
37
|
+
: undefined;
|
|
35
38
|
const imageCost = perImage != null
|
|
36
39
|
? imageCount * perImage
|
|
37
40
|
: price.imageInputPer1K
|
|
@@ -45,43 +48,95 @@ export function usageTracker(prices, opts = {}) {
|
|
|
45
48
|
}
|
|
46
49
|
}
|
|
47
50
|
if (opts.logPerCall)
|
|
48
|
-
ctx.log.info?.(
|
|
49
|
-
promptTokens: p,
|
|
50
|
-
|
|
51
|
+
ctx.log.info?.("[usage] call", {
|
|
52
|
+
promptTokens: p,
|
|
53
|
+
completionTokens: c,
|
|
54
|
+
totalTokens: t,
|
|
55
|
+
imageTokens: imageCount > 0 && price?.imageInputPer1K
|
|
56
|
+
? imageTokens
|
|
57
|
+
: undefined,
|
|
51
58
|
imageCount: imageCount > 0 ? imageCount : undefined,
|
|
52
|
-
estCostUSD: price
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
59
|
+
estCostUSD: price
|
|
60
|
+
? (() => {
|
|
61
|
+
const inPer1K = price.inputPer1K ??
|
|
62
|
+
(price.inputPer1M != null ? price.inputPer1M / 1000 : 0);
|
|
63
|
+
const outPer1K = price.outputPer1K ??
|
|
64
|
+
(price.outputPer1M != null ? price.outputPer1M / 1000 : 0);
|
|
65
|
+
const textPromptTokens = Math.max(0, p - (price?.imageInputPer1K ? imageTokens : 0));
|
|
66
|
+
const perImage = price?.imagePerImage != null
|
|
67
|
+
? price.imagePerImage
|
|
68
|
+
: price?.imagePer1K != null
|
|
69
|
+
? price.imagePer1K / 1000
|
|
70
|
+
: undefined;
|
|
71
|
+
const tokenCost = (textPromptTokens / 1000) * inPer1K + (c / 1000) * outPer1K;
|
|
72
|
+
const imageCost = perImage != null
|
|
73
|
+
? imageCount * perImage
|
|
74
|
+
: price?.imageInputPer1K
|
|
75
|
+
? (imageTokens / 1000) * price.imageInputPer1K
|
|
76
|
+
: 0;
|
|
77
|
+
return roundUSD(tokenCost + imageCost);
|
|
78
|
+
})()
|
|
79
|
+
: undefined,
|
|
61
80
|
});
|
|
62
81
|
}
|
|
63
|
-
return out;
|
|
64
82
|
}
|
|
65
|
-
|
|
66
|
-
|
|
83
|
+
const isAsyncIterable = (val) => !!val &&
|
|
84
|
+
typeof val[Symbol.asyncIterator] ===
|
|
85
|
+
"function";
|
|
86
|
+
function withUsage(...args) {
|
|
87
|
+
const reqMessages = args?.[0];
|
|
88
|
+
const imageCount = countImageInputs(reqMessages);
|
|
89
|
+
const imageTokens = imageCount * Number(price?.imageTokenPerImage ?? 1000);
|
|
90
|
+
const out = orig(...args);
|
|
91
|
+
if (isAsyncIterable(out)) {
|
|
92
|
+
const iter = async function* () {
|
|
93
|
+
let final;
|
|
94
|
+
for await (const ev of out) {
|
|
95
|
+
if (ev.type === "assistant_message") {
|
|
96
|
+
final = { message: ev.message };
|
|
97
|
+
}
|
|
98
|
+
else if (ev.type === "usage") {
|
|
99
|
+
final = {
|
|
100
|
+
message: { role: "assistant", content: "" },
|
|
101
|
+
usage: ev.usage,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
yield ev;
|
|
105
|
+
}
|
|
106
|
+
if (final)
|
|
107
|
+
applyUsage(final, imageCount, imageTokens);
|
|
108
|
+
};
|
|
109
|
+
return iter();
|
|
110
|
+
}
|
|
111
|
+
return (async () => {
|
|
112
|
+
const resolved = await out;
|
|
113
|
+
applyUsage(resolved, imageCount, imageTokens);
|
|
114
|
+
return resolved;
|
|
115
|
+
})();
|
|
116
|
+
}
|
|
117
|
+
state._origGenerate = orig;
|
|
118
|
+
ctx.model.generate =
|
|
119
|
+
withUsage;
|
|
67
120
|
await next();
|
|
68
121
|
// Restore
|
|
69
122
|
ctx.model.generate = orig;
|
|
70
|
-
if (!
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
123
|
+
if (!state.usage)
|
|
124
|
+
state.usage = {};
|
|
125
|
+
state.usage.promptTokens = totals.promptTokens;
|
|
126
|
+
state.usage.completionTokens = totals.completionTokens;
|
|
127
|
+
state.usage.totalTokens = totals.totalTokens;
|
|
75
128
|
if (price)
|
|
76
|
-
|
|
129
|
+
state.usage.costUSD = roundUSD(totals.costUSD ?? 0);
|
|
77
130
|
if (totals.imageTokens)
|
|
78
|
-
|
|
131
|
+
state.usage.imageTokens = totals.imageTokens;
|
|
79
132
|
if (totals.imageCount)
|
|
80
|
-
|
|
81
|
-
ctx.log.info?.(
|
|
133
|
+
state.usage.imageCount = totals.imageCount;
|
|
134
|
+
ctx.log.info?.("[usage] totals", state.usage);
|
|
82
135
|
};
|
|
83
136
|
}
|
|
84
|
-
function roundUSD(n) {
|
|
137
|
+
function roundUSD(n) {
|
|
138
|
+
return Math.round(n * 1e6) / 1e6;
|
|
139
|
+
}
|
|
85
140
|
function countImageInputs(msgs) {
|
|
86
141
|
if (!Array.isArray(msgs))
|
|
87
142
|
return 0;
|
|
@@ -90,16 +145,20 @@ function countImageInputs(msgs) {
|
|
|
90
145
|
const c = m?.content;
|
|
91
146
|
if (Array.isArray(c)) {
|
|
92
147
|
for (const part of c) {
|
|
93
|
-
|
|
148
|
+
const partType = part?.type;
|
|
149
|
+
if (part &&
|
|
150
|
+
typeof part === "object" &&
|
|
151
|
+
(partType === "image_url" || partType === "image"))
|
|
94
152
|
count += 1;
|
|
95
153
|
}
|
|
96
154
|
}
|
|
97
155
|
// Convenience shapes supported by adapters (count regardless of content presence)
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
156
|
+
const anyMsg = m;
|
|
157
|
+
if (Array.isArray(anyMsg.images))
|
|
158
|
+
count += anyMsg.images.length;
|
|
159
|
+
if (typeof anyMsg.image_url === "string")
|
|
101
160
|
count += 1;
|
|
102
|
-
if (typeof
|
|
161
|
+
if (typeof anyMsg.image === "string")
|
|
103
162
|
count += 1;
|
|
104
163
|
}
|
|
105
164
|
return count;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sisu-ai/mw-usage-tracker",
|
|
3
|
-
"version": "9.0.
|
|
3
|
+
"version": "9.0.3",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"access": "public"
|
|
13
13
|
},
|
|
14
14
|
"peerDependencies": {
|
|
15
|
-
"@sisu-ai/core": "^2.3.
|
|
15
|
+
"@sisu-ai/core": "^2.3.2"
|
|
16
16
|
},
|
|
17
17
|
"repository": {
|
|
18
18
|
"type": "git",
|