@columenlabs/lumen-ops 0.1.34 → 0.1.36
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 +31 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/tools.d.ts +49 -0
- package/dist/tools.js +67 -0
- package/package.json +6 -2
package/README.md
CHANGED
|
@@ -93,6 +93,37 @@ GROUP BY fingerprint ORDER BY p95_ms DESC LIMIT 20
|
|
|
93
93
|
| `queryShape(sql)` | The shape, fingerprint and op of a query |
|
|
94
94
|
| `foldRoute(path)` | A path with id-like segments as `:id` |
|
|
95
95
|
|
|
96
|
+
## Tool calls
|
|
97
|
+
|
|
98
|
+
`@columenlabs/lumen-ops/tools` meters each call an assistant or agent makes: one point per call with its latency, outcome and a hash of the code it ran, so slow or failing tools show up and the same script is grouped across runs.
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
import { meteredCall, meterToolCall, setToolSink } from '@columenlabs/lumen-ops/tools';
|
|
102
|
+
|
|
103
|
+
setToolSink(env.TOOL_CALLS); // an Analytics Engine dataset binding
|
|
104
|
+
|
|
105
|
+
// Wrap a call: latency and outcome are taken for you.
|
|
106
|
+
const rows = await meteredCall({ app: 'trade', tool: 'portfolio_list', kind: 'call' }, () => run(args));
|
|
107
|
+
|
|
108
|
+
// Or write one by hand, with the code a script ran.
|
|
109
|
+
meterToolCall({ app: 'trade', tool: 'script', kind: 'script', ok: true, ms: 840, code: source, rows: 120 });
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
| Column | Holds |
|
|
113
|
+
|---|---|
|
|
114
|
+
| `index1` | app |
|
|
115
|
+
| `blob1`…`blob9` | app, tool, kind, outcome (`ok` or `error`), error kind, code hash, source, tier, run id |
|
|
116
|
+
| `double1`…`double6` | ms, rows, bytes in, bytes out, tokens in, tokens out |
|
|
117
|
+
|
|
118
|
+
Arguments and results are never written; only their sizes.
|
|
119
|
+
|
|
120
|
+
| Export | What it does |
|
|
121
|
+
|---|---|
|
|
122
|
+
| `setToolSink(binding)` | The dataset tool points go to; nothing is written until it is set |
|
|
123
|
+
| `meterToolCall(point)` | One point by hand |
|
|
124
|
+
| `meteredCall(point, fn, errorKind?)` | Runs `fn`, timing it and writing its outcome |
|
|
125
|
+
| `codeHash(code)` | A short hash of a script, whitespace folded |
|
|
126
|
+
|
|
96
127
|
## Versions
|
|
97
128
|
|
|
98
129
|
0.x: any minor may break things until 1.0. Every merge to Lumen's `main` publishes the next patch of every Lumen package; see the [releases](https://github.com/columenlabs/lumen/releases).
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
package/dist/tools.d.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The tool-call meter: every tool an assistant or an agent calls (an MCP tool, a function, a script
|
|
3
|
+
* in a sandbox, a web read) is one Analytics Engine point, with how long it took, how it ended and
|
|
4
|
+
* a hash of the code it ran, so slow, failing and repeated calls can be found and made first class.
|
|
5
|
+
*
|
|
6
|
+
* A point holds names, numbers and hashes only: never the arguments, the result or the person.
|
|
7
|
+
*/
|
|
8
|
+
import type { PointSink } from './points.js';
|
|
9
|
+
export interface ToolCallPoint {
|
|
10
|
+
/** The app, or the part of it, the call ran in. */
|
|
11
|
+
app: string;
|
|
12
|
+
/** The tool or function called (`trade.portfolio_list`), or a name for the script. */
|
|
13
|
+
tool: string;
|
|
14
|
+
/** How it was called: call, script, sql, web, or your own. */
|
|
15
|
+
kind: string;
|
|
16
|
+
ok: boolean;
|
|
17
|
+
/** Wall time of the call, ms. */
|
|
18
|
+
ms: number;
|
|
19
|
+
/** A short error kind when it failed (`bad_args`, `timeout`), never the message. */
|
|
20
|
+
error?: string;
|
|
21
|
+
/** The code the call ran (a script's source, a query); only its hash is written. */
|
|
22
|
+
code?: string;
|
|
23
|
+
/** Who started it: chat, brief, job, or your own. */
|
|
24
|
+
source?: string;
|
|
25
|
+
/** The model size or tier that made the call, never a model's name if the app keeps it private. */
|
|
26
|
+
tier?: string;
|
|
27
|
+
/** The turn or run the call belongs to, an id the app makes (not a person's). */
|
|
28
|
+
run?: string;
|
|
29
|
+
/** Rows the call returned. */
|
|
30
|
+
rows?: number;
|
|
31
|
+
/** Bytes of the call's input and output. */
|
|
32
|
+
bytesIn?: number;
|
|
33
|
+
bytesOut?: number;
|
|
34
|
+
/** Tokens, when the call itself ran a model. */
|
|
35
|
+
tokensIn?: number;
|
|
36
|
+
tokensOut?: number;
|
|
37
|
+
}
|
|
38
|
+
/** The entry point sets the dataset once per isolate. */
|
|
39
|
+
export declare function setToolSink(s: PointSink | undefined): void;
|
|
40
|
+
/** A 64-bit hash of the code with its whitespace folded: the same script hashes the same. */
|
|
41
|
+
export declare function codeHash(code: string): string;
|
|
42
|
+
/**
|
|
43
|
+
* Column order is the contract with whoever reads the dataset back: index1 app; blobs app, tool,
|
|
44
|
+
* kind, outcome, error, code hash, source, tier, run; doubles ms, rows, bytes in, bytes out,
|
|
45
|
+
* tokens in, tokens out.
|
|
46
|
+
*/
|
|
47
|
+
export declare function meterToolCall(p: ToolCallPoint): void;
|
|
48
|
+
/** Runs one tool call and meters it: the wall time, and `error` as the kind when it throws. */
|
|
49
|
+
export declare function meteredCall<T>(p: Omit<ToolCallPoint, 'ok' | 'ms'>, fn: () => Promise<T>, errorKind?: (err: unknown) => string): Promise<T>;
|
package/dist/tools.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
let sink;
|
|
2
|
+
/** The entry point sets the dataset once per isolate. */
|
|
3
|
+
export function setToolSink(s) {
|
|
4
|
+
sink = s;
|
|
5
|
+
}
|
|
6
|
+
const fnv = (s, seed) => {
|
|
7
|
+
let h = seed >>> 0;
|
|
8
|
+
for (let i = 0; i < s.length; i++)
|
|
9
|
+
h = Math.imul(h ^ s.charCodeAt(i), 16777619) >>> 0;
|
|
10
|
+
return h.toString(16).padStart(8, '0');
|
|
11
|
+
};
|
|
12
|
+
/** A 64-bit hash of the code with its whitespace folded: the same script hashes the same. */
|
|
13
|
+
export function codeHash(code) {
|
|
14
|
+
const s = code.replace(/\s+/g, ' ').trim();
|
|
15
|
+
return fnv(s, 2166136261) + fnv(s, 0x9e3779b9);
|
|
16
|
+
}
|
|
17
|
+
const cap = (s, n = 96) => (s.length > n ? s.slice(0, n) : s);
|
|
18
|
+
const num = (v) => (typeof v === 'number' && Number.isFinite(v) ? v : 0);
|
|
19
|
+
/**
|
|
20
|
+
* Column order is the contract with whoever reads the dataset back: index1 app; blobs app, tool,
|
|
21
|
+
* kind, outcome, error, code hash, source, tier, run; doubles ms, rows, bytes in, bytes out,
|
|
22
|
+
* tokens in, tokens out.
|
|
23
|
+
*/
|
|
24
|
+
export function meterToolCall(p) {
|
|
25
|
+
if (!sink)
|
|
26
|
+
return;
|
|
27
|
+
try {
|
|
28
|
+
sink.writeDataPoint({
|
|
29
|
+
indexes: [cap(p.app)],
|
|
30
|
+
blobs: [
|
|
31
|
+
cap(p.app),
|
|
32
|
+
cap(p.tool, 128),
|
|
33
|
+
cap(p.kind),
|
|
34
|
+
p.ok ? 'ok' : 'error',
|
|
35
|
+
cap(p.error ?? ''),
|
|
36
|
+
p.code ? codeHash(p.code) : '',
|
|
37
|
+
cap(p.source ?? ''),
|
|
38
|
+
cap(p.tier ?? ''),
|
|
39
|
+
cap(p.run ?? ''),
|
|
40
|
+
],
|
|
41
|
+
doubles: [
|
|
42
|
+
num(p.ms),
|
|
43
|
+
num(p.rows),
|
|
44
|
+
num(p.bytesIn),
|
|
45
|
+
num(p.bytesOut),
|
|
46
|
+
num(p.tokensIn),
|
|
47
|
+
num(p.tokensOut),
|
|
48
|
+
],
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
catch (err) {
|
|
52
|
+
console.error('meterToolCall', err);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/** Runs one tool call and meters it: the wall time, and `error` as the kind when it throws. */
|
|
56
|
+
export async function meteredCall(p, fn, errorKind = () => 'error') {
|
|
57
|
+
const t0 = performance.now();
|
|
58
|
+
try {
|
|
59
|
+
const out = await fn();
|
|
60
|
+
meterToolCall({ ...p, ok: true, ms: performance.now() - t0 });
|
|
61
|
+
return out;
|
|
62
|
+
}
|
|
63
|
+
catch (err) {
|
|
64
|
+
meterToolCall({ ...p, ok: false, ms: performance.now() - t0, error: errorKind(err) });
|
|
65
|
+
throw err;
|
|
66
|
+
}
|
|
67
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@columenlabs/lumen-ops",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "Lumen's ops meters: every data query an app runs as one Analytics Engine point, by
|
|
3
|
+
"version": "0.1.36",
|
|
4
|
+
"description": "Lumen's ops meters: every data query and every tool call an app runs as one Analytics Engine point, by shape and hash only.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "./dist/index.js",
|
|
@@ -18,6 +18,10 @@
|
|
|
18
18
|
"./queries": {
|
|
19
19
|
"types": "./dist/queries.d.ts",
|
|
20
20
|
"default": "./dist/queries.js"
|
|
21
|
+
},
|
|
22
|
+
"./tools": {
|
|
23
|
+
"types": "./dist/tools.d.ts",
|
|
24
|
+
"default": "./dist/tools.js"
|
|
21
25
|
}
|
|
22
26
|
},
|
|
23
27
|
"files": [
|