Skip to content

Commit e2ffa22

Browse files
committed
feat: Add limit processor
Limit text or tool output by approximate token count to manage content size. Increases version to 0.2.2.
1 parent 633b136 commit e2ffa22

3 files changed

Lines changed: 108 additions & 1 deletion

File tree

README.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ Basic toolset for [Tune](https://github.com/iovdin/tune).
4141
- [slice](#slice) take lines from <start> to <finish> of a file
4242
- [random](#random) random selection, sampling, shuffling, uniform ranges
4343
- [curry](#curry) change a tool by setting a parameter
44+
- [limit](#limit) limit output size by approximate token count
4445

4546

4647
## Setup
@@ -843,6 +844,31 @@ Notes:
843844
- choices and sample output multiple lines (one item per line).
844845

845846

847+
### `limit`
848+
Limit text or tool output by approximate token count.
849+
850+
Useful when including large files or command output into a prompt.
851+
The processor estimates tokens as roughly `content.length / 4`.
852+
853+
Arguments:
854+
- `tokens` max token limit, default `10000`
855+
- `hit` what to do when the limit is exceeded:
856+
- `hard_err` throw an error (default)
857+
- `soft_err` return a short message instead of the content
858+
- `cut` truncate the content and append a warning
859+
860+
`tokens` also supports shorthand values like `2k`, `1.5k`, `3m`.
861+
Invalid, non-finite, or non-positive values fall back to `10000`.
862+
863+
```chat
864+
system:
865+
@{ README.md | limit tokens=2k }
866+
867+
@{ sh | limit tokens=1500 hit=soft_err }
868+
@{ sh | limit tokens=1.5k hit=cut }
869+
@{| proc sh tree | limit tokens=800 }
870+
```
871+
846872
### `curry`
847873
Modify a tool by setting parameter or name or description.
848874
Narrow possible usage of a tool so that LLM wont mess up

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "tune-basic-toolset",
3-
"version": "0.2.1",
3+
"version": "0.2.2",
44
"description": "Basic toolset for tune",
55
"main": "src/index.js",
66
"files": [

src/limit.proc.js

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
const { parseArgs } = require('./utils.js')
2+
3+
4+
const parseMaxTokens = (value) => {
5+
if (value == null || value === '') {
6+
return 10000
7+
}
8+
if (typeof value === 'number') {
9+
return Number.isFinite(value) && value > 0 ? Math.floor(value) : 10000
10+
}
11+
if (typeof value === 'string') {
12+
const normalized = value.trim().toLowerCase()
13+
const match = normalized.match(/^([+-]?\d+(?:\.\d+)?)\s*([kmb])?$/)
14+
if (!match) {
15+
return 10000
16+
}
17+
18+
const num = Number(match[1])
19+
if (!Number.isFinite(num) || num <= 0) {
20+
return 10000
21+
}
22+
23+
const multipliers = {
24+
k: 1e3,
25+
m: 1e6,
26+
b: 1e9,
27+
}
28+
const multiplier = multipliers[match[2]] || 1
29+
const parsed = Math.floor(num * multiplier)
30+
return Number.isFinite(parsed) && parsed > 0 ? parsed : 10000
31+
}
32+
return 10000
33+
}
34+
35+
module.exports = async function limit(node, args) {
36+
/*
37+
@{ sh | limit tokens=2000 hit=hard_err|soft_err|cut }
38+
@{ filename | limit tokens=2k }
39+
@{ filename | limit tokens=1.5K }
40+
@{| proc sh tree | limit }
41+
*/
42+
43+
if (!node) {
44+
return
45+
}
46+
47+
const params = parseArgs(args)
48+
49+
const maxTokens = parseMaxTokens(params.tokens)
50+
51+
const handleContent = (content) => {
52+
if (!content || (content.length / 4) <= maxTokens) {
53+
return content
54+
}
55+
switch (params.hit) {
56+
case "cut":
57+
//TODO: binary
58+
return content.slice(0, maxTokens * 4) + `\n warning: the rest of the content is cut because it hit max token limit ${maxTokens} `
59+
case "soft_err":
60+
return `Content is too big to be shown, context limit ${maxTokens} tokens`
61+
default: // hard_err
62+
throw Error(`Content is too big to be shown, context limit ${maxTokens} tokens`)
63+
}
64+
}
65+
66+
if (node.type === "text") {
67+
return {
68+
...node,
69+
read: async () => handleContent(await node.read())
70+
}
71+
}
72+
73+
if (node.type === "tool") {
74+
return {
75+
...node,
76+
exec: async (params, ctx) => handleContent(await node.exec.call(ctx, params, ctx))
77+
}
78+
}
79+
80+
throw Error(`limit processor can only handle 'text' and 'tool' nodes, got '${node.type}'`)
81+
}

0 commit comments

Comments
 (0)