seerlens 0.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.
- package/README.md +32 -0
- package/index.js +103 -0
- package/package.json +16 -0
package/README.md
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# seerlens (JavaScript)
|
|
2
|
+
|
|
3
|
+
Send your Node app's LLM calls to [Seerlens](https://github.com/eladser/seerlens). Traces go out as OpenTelemetry GenAI spans, so they land in the same dashboard as the .NET and Python ones.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm install seerlens
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
```js
|
|
10
|
+
import * as seerlens from 'seerlens'
|
|
11
|
+
|
|
12
|
+
seerlens.configure('http://localhost:5005')
|
|
13
|
+
|
|
14
|
+
const span = seerlens.trace('answer ticket', { model: 'gpt-4o' })
|
|
15
|
+
const reply = await myLlm(prompt)
|
|
16
|
+
span.complete({ prompt, completion: reply, inputTokens: 40, outputTokens: 12 })
|
|
17
|
+
|
|
18
|
+
await seerlens.flush() // before a short script exits
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Or record a call you already made:
|
|
22
|
+
|
|
23
|
+
```js
|
|
24
|
+
seerlens.record({ model: 'gpt-4o', prompt: 'hi', completion: 'hello', inputTokens: 10, outputTokens: 5, durationMs: 820 })
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Sends are fire-and-forget and errors are swallowed, so this never blocks or throws into your app. No dependencies, needs Node 18+.
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
node example.js # against a running collector
|
|
31
|
+
npm test
|
|
32
|
+
```
|
package/index.js
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
// Send your JS app's LLM calls to Seerlens.
|
|
2
|
+
//
|
|
3
|
+
// import * as seerlens from 'seerlens'
|
|
4
|
+
// seerlens.configure('http://localhost:5005')
|
|
5
|
+
//
|
|
6
|
+
// const span = seerlens.trace('answer ticket', { model: 'gpt-4o' })
|
|
7
|
+
// const reply = await myLlm(prompt)
|
|
8
|
+
// span.complete({ prompt, completion: reply, inputTokens: 40, outputTokens: 12 })
|
|
9
|
+
//
|
|
10
|
+
// await seerlens.flush() // before a short script exits
|
|
11
|
+
//
|
|
12
|
+
// Traces go out as OpenTelemetry GenAI spans. Sends are fire-and-forget and
|
|
13
|
+
// errors are swallowed, so this never blocks or breaks your app.
|
|
14
|
+
|
|
15
|
+
import { randomBytes } from 'node:crypto'
|
|
16
|
+
|
|
17
|
+
let endpoint = null
|
|
18
|
+
const pending = new Set()
|
|
19
|
+
|
|
20
|
+
export function configure(collectorUrl) {
|
|
21
|
+
endpoint = collectorUrl.replace(/\/+$/, '') + '/v1/traces'
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function record({
|
|
25
|
+
model,
|
|
26
|
+
prompt = '',
|
|
27
|
+
completion = '',
|
|
28
|
+
inputTokens,
|
|
29
|
+
outputTokens,
|
|
30
|
+
durationMs = 0,
|
|
31
|
+
system,
|
|
32
|
+
name,
|
|
33
|
+
} = {}) {
|
|
34
|
+
send(buildPayload({ model, prompt, completion, inputTokens, outputTokens, durationMs, system, name }))
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Times a call. Call complete() once you have the response.
|
|
38
|
+
export function trace(name, { model, system } = {}) {
|
|
39
|
+
const start = performance.now()
|
|
40
|
+
return {
|
|
41
|
+
complete({ prompt = '', completion = '', inputTokens, outputTokens } = {}) {
|
|
42
|
+
record({ model, system, name, prompt, completion, inputTokens, outputTokens, durationMs: performance.now() - start })
|
|
43
|
+
},
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export async function flush() {
|
|
48
|
+
await Promise.allSettled([...pending])
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function buildPayload({ model, prompt, completion, inputTokens, outputTokens, durationMs, system, name }) {
|
|
52
|
+
const end = BigInt(Date.now()) * 1_000_000n
|
|
53
|
+
const start = end - BigInt(Math.round((durationMs || 0) * 1e6))
|
|
54
|
+
const span = {
|
|
55
|
+
traceId: hexId(16),
|
|
56
|
+
spanId: hexId(8),
|
|
57
|
+
parentSpanId: '',
|
|
58
|
+
name: name || `chat: ${model}`,
|
|
59
|
+
startTimeUnixNano: start.toString(),
|
|
60
|
+
endTimeUnixNano: end.toString(),
|
|
61
|
+
attributes: attrs({ model, prompt, completion, inputTokens, outputTokens, system }),
|
|
62
|
+
status: { code: 1 },
|
|
63
|
+
}
|
|
64
|
+
return { resourceSpans: [{ scopeSpans: [{ spans: [span] }] }] }
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function send(payload) {
|
|
68
|
+
if (!endpoint) return
|
|
69
|
+
const p = fetch(endpoint, {
|
|
70
|
+
method: 'POST',
|
|
71
|
+
headers: { 'content-type': 'application/json' },
|
|
72
|
+
body: JSON.stringify(payload),
|
|
73
|
+
})
|
|
74
|
+
.then(() => {})
|
|
75
|
+
.catch(() => {}) // never break the host
|
|
76
|
+
.finally(() => pending.delete(p))
|
|
77
|
+
pending.add(p)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function attrs({ model, prompt, completion, inputTokens, outputTokens, system }) {
|
|
81
|
+
const out = []
|
|
82
|
+
const str = (k, v) => { if (v) out.push({ key: k, value: { stringValue: String(v) } }) }
|
|
83
|
+
const int = (k, v) => { if (v != null) out.push({ key: k, value: { intValue: String(v) } }) }
|
|
84
|
+
str('gen_ai.system', system || provider(model))
|
|
85
|
+
str('gen_ai.request.model', model)
|
|
86
|
+
str('gen_ai.prompt', prompt)
|
|
87
|
+
str('gen_ai.completion', completion)
|
|
88
|
+
int('gen_ai.usage.input_tokens', inputTokens)
|
|
89
|
+
int('gen_ai.usage.output_tokens', outputTokens)
|
|
90
|
+
return out
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function provider(model) {
|
|
94
|
+
const m = (model || '').toLowerCase()
|
|
95
|
+
if (m.startsWith('gpt') || m.startsWith('o1') || m.startsWith('o3')) return 'openai'
|
|
96
|
+
if (m.includes('claude')) return 'anthropic'
|
|
97
|
+
if (m.includes('gemini')) return 'google'
|
|
98
|
+
return null
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function hexId(n) {
|
|
102
|
+
return randomBytes(n).toString('hex')
|
|
103
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "seerlens",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Send your JS app's LLM calls to Seerlens, the local DevTools for AI calls.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "index.js",
|
|
7
|
+
"exports": "./index.js",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"test": "node --test"
|
|
10
|
+
},
|
|
11
|
+
"keywords": ["llm", "ai", "observability", "tracing", "opentelemetry"],
|
|
12
|
+
"author": "Elad Sertshuk",
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"homepage": "https://github.com/eladser/seerlens",
|
|
15
|
+
"files": ["index.js", "README.md"]
|
|
16
|
+
}
|