@farzanhossans/agentlens 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 +151 -0
- package/dist/index.d.mts +59 -0
- package/dist/index.d.ts +59 -0
- package/dist/index.js +1103 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +1082 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +63 -0
package/README.md
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# @farzanhossans/agentlens
|
|
2
|
+
|
|
3
|
+
Universal AI agent observability — **one line** to trace every LLM call.
|
|
4
|
+
|
|
5
|
+
Patches `globalThis.fetch` and Node's `http`/`https` so every call to OpenAI,
|
|
6
|
+
Anthropic, Gemini, Cohere, or Mistral is captured automatically. No SDK
|
|
7
|
+
wrappers. No code changes inside your call sites.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pnpm add @farzanhossans/agentlens
|
|
13
|
+
# or: npm i @farzanhossans/agentlens
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Quickstart
|
|
17
|
+
|
|
18
|
+
### 1. Cloud — zero config (default)
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
import { AgentLens } from '@farzanhossans/agentlens'
|
|
22
|
+
|
|
23
|
+
AgentLens.init({ apiKey: 'proj_xxx' })
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### 2. Self-hosted — point at your own ingest endpoint
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
import { AgentLens } from '@farzanhossans/agentlens'
|
|
30
|
+
|
|
31
|
+
AgentLens.init({
|
|
32
|
+
apiKey: 'proj_xxx', // key from your own AgentLens dashboard
|
|
33
|
+
endpoint: 'https://agentlens.your-company.com/v1/spans',
|
|
34
|
+
})
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### 3. Self-hosted + debug mode (logs each captured span to stdout)
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
AgentLens.init({
|
|
41
|
+
apiKey: 'proj_xxx',
|
|
42
|
+
endpoint: 'https://agentlens.your-company.com/v1/spans',
|
|
43
|
+
debug: true,
|
|
44
|
+
})
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### 4. Disable PII scrubbing (only for trusted internal data)
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
AgentLens.init({
|
|
51
|
+
apiKey: 'proj_xxx',
|
|
52
|
+
pii: false,
|
|
53
|
+
})
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## What gets captured
|
|
57
|
+
|
|
58
|
+
For every matched LLM HTTP call:
|
|
59
|
+
|
|
60
|
+
- `provider` and `model`
|
|
61
|
+
- `inputTokens`, `outputTokens`, `totalTokens`
|
|
62
|
+
- `costUsd` (calculated from a built-in price table per provider)
|
|
63
|
+
- `inputText` and `outputText` (PII-scrubbed by default)
|
|
64
|
+
- `latency` and `status`
|
|
65
|
+
- `isStream` flag — streaming responses are tapped via `ReadableStream.tee()` so
|
|
66
|
+
the stream you read is byte-identical to the original
|
|
67
|
+
|
|
68
|
+
## Supported providers
|
|
69
|
+
|
|
70
|
+
All five providers support both streaming and non-streaming responses.
|
|
71
|
+
|
|
72
|
+
| Provider | Hosts | Endpoints | Streaming |
|
|
73
|
+
| --------- | -------------------------------------- | -------------------------------------------------------------- | --------- |
|
|
74
|
+
| OpenAI | `api.openai.com` | `/v1/chat/completions`, `/v1/completions`, `/v1/embeddings` | ✅ |
|
|
75
|
+
| Anthropic | `api.anthropic.com` | `/v1/messages` | ✅ |
|
|
76
|
+
| Gemini | `generativelanguage.googleapis.com` | `/v1beta/models`, `/v1/models` | ✅ |
|
|
77
|
+
| Cohere | `api.cohere.com` | `/v1/chat`, `/v1/generate`, `/v2/chat` (v1 + v2 stream shapes) | ✅ |
|
|
78
|
+
| Mistral | `api.mistral.ai` | `/v1/chat/completions` | ✅ |
|
|
79
|
+
|
|
80
|
+
## Grouping calls with `trace()`
|
|
81
|
+
|
|
82
|
+
Wrap a logical unit of work (an agent step, a multi-call retrieval, etc.) in
|
|
83
|
+
`AgentLens.trace(name, fn)`. Every LLM call inside `fn` — including async ones
|
|
84
|
+
and across nested traces — is auto-tagged with the same `traceId` and gets
|
|
85
|
+
`parentSpanId` set to the trace's span. Built on Node's `AsyncLocalStorage`,
|
|
86
|
+
so it composes with `Promise.all`, `setTimeout`, async generators, etc.
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
import { AgentLens } from '@farzanhossans/agentlens'
|
|
90
|
+
|
|
91
|
+
AgentLens.init({ apiKey: 'proj_xxx' })
|
|
92
|
+
|
|
93
|
+
await AgentLens.trace('classify-then-rephrase', async () => {
|
|
94
|
+
const classification = await openai.chat.completions.create({ ... })
|
|
95
|
+
// ↑ span emitted with parentSpanId = the trace's spanId
|
|
96
|
+
const rephrased = await anthropic.messages.create({ ... })
|
|
97
|
+
// ↑ same trace, same parentSpanId
|
|
98
|
+
})
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Nested traces nest:
|
|
102
|
+
|
|
103
|
+
```ts
|
|
104
|
+
await AgentLens.trace('outer', async () => {
|
|
105
|
+
await AgentLens.trace('inner', async () => {
|
|
106
|
+
await openai.chat.completions.create({ ... })
|
|
107
|
+
// child of `inner`, which is child of `outer`, all sharing one traceId
|
|
108
|
+
})
|
|
109
|
+
})
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## PII scrubbing
|
|
113
|
+
|
|
114
|
+
Enabled by default. Strips emails, phone numbers, SSNs, credit cards, IPv4
|
|
115
|
+
addresses, and obvious API-key shapes from `inputText` / `outputText` before
|
|
116
|
+
spans leave your process. Pass `pii: false` to disable.
|
|
117
|
+
|
|
118
|
+
## Self-hosted flow
|
|
119
|
+
|
|
120
|
+
1. Deploy AgentLens via Docker Compose on your own server
|
|
121
|
+
2. Open your own dashboard → create a project → copy the API key
|
|
122
|
+
3. `AgentLens.init({ apiKey, endpoint })` pointing at your own ingest URL
|
|
123
|
+
4. Spans flow: your app → your worker → your DB. Nothing touches AgentLens cloud.
|
|
124
|
+
|
|
125
|
+
## API
|
|
126
|
+
|
|
127
|
+
```ts
|
|
128
|
+
AgentLens.init(config: {
|
|
129
|
+
apiKey: string
|
|
130
|
+
endpoint?: string // default: https://ingest.agentlens.dev/v1/spans
|
|
131
|
+
debug?: boolean // default: false
|
|
132
|
+
pii?: boolean // default: true
|
|
133
|
+
flushIntervalMs?: number // default: 500
|
|
134
|
+
maxBatchSize?: number // default: 50
|
|
135
|
+
}): void
|
|
136
|
+
|
|
137
|
+
AgentLens.flush(): Promise<void> // force-flush pending spans
|
|
138
|
+
AgentLens.shutdown(): void // stop the flush timer
|
|
139
|
+
|
|
140
|
+
AgentLens.trace<T>(name: string, fn: () => Promise<T>): Promise<T>
|
|
141
|
+
// re-exports from @farzanhossans/agentlens-core:
|
|
142
|
+
getCurrentTrace(), getCurrentTraceId(), getCurrentSpanId(), runWithTrace(ctx, fn)
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Requirements
|
|
146
|
+
|
|
147
|
+
Node ≥ 18 (the SDK uses `AsyncLocalStorage`, `globalThis.fetch`, and `crypto.randomUUID`).
|
|
148
|
+
|
|
149
|
+
## License
|
|
150
|
+
|
|
151
|
+
MIT
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
export { getCurrentSpanId, getCurrentTrace, getCurrentTraceId, runWithTrace } from '@farzanhossans/agentlens-core';
|
|
2
|
+
|
|
3
|
+
type ParserName = 'openai' | 'anthropic' | 'gemini' | 'cohere' | 'mistral';
|
|
4
|
+
interface LLMEndpoint {
|
|
5
|
+
provider: string;
|
|
6
|
+
parser: ParserName;
|
|
7
|
+
paths: string[];
|
|
8
|
+
}
|
|
9
|
+
interface ParsedSpan {
|
|
10
|
+
model: string;
|
|
11
|
+
provider: string;
|
|
12
|
+
inputTokens: number;
|
|
13
|
+
outputTokens: number;
|
|
14
|
+
totalTokens: number;
|
|
15
|
+
costUsd: number;
|
|
16
|
+
inputText: string;
|
|
17
|
+
outputText: string;
|
|
18
|
+
isStream: boolean;
|
|
19
|
+
error?: string;
|
|
20
|
+
}
|
|
21
|
+
interface AgentLensConfig {
|
|
22
|
+
apiKey: string;
|
|
23
|
+
endpoint?: string;
|
|
24
|
+
debug?: boolean;
|
|
25
|
+
pii?: boolean;
|
|
26
|
+
flushIntervalMs?: number;
|
|
27
|
+
maxBatchSize?: number;
|
|
28
|
+
}
|
|
29
|
+
interface OutboundSpan extends ParsedSpan {
|
|
30
|
+
spanId: string;
|
|
31
|
+
traceId: string;
|
|
32
|
+
parentSpanId?: string;
|
|
33
|
+
timestamp: string;
|
|
34
|
+
latency: number;
|
|
35
|
+
status: number;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
declare const LLM_REGISTRY: Record<string, LLMEndpoint>;
|
|
39
|
+
declare function matchLLM(url: string): LLMEndpoint | null;
|
|
40
|
+
|
|
41
|
+
declare function scrubPII(text: string): string;
|
|
42
|
+
declare function scrubObject(obj: unknown): unknown;
|
|
43
|
+
|
|
44
|
+
declare const AgentLens: {
|
|
45
|
+
init(config: AgentLensConfig): void;
|
|
46
|
+
flush(): Promise<void>;
|
|
47
|
+
shutdown(): void;
|
|
48
|
+
/**
|
|
49
|
+
* Wraps `fn` in a named trace context. Any LLM calls made inside `fn`
|
|
50
|
+
* (including async ones) are auto-tagged with this trace's `traceId` and
|
|
51
|
+
* get `parentSpanId` set to this trace's `spanId`. Nested `trace()` calls
|
|
52
|
+
* become child spans automatically.
|
|
53
|
+
*
|
|
54
|
+
* Use this to group multiple LLM calls that belong to the same agent run.
|
|
55
|
+
*/
|
|
56
|
+
trace<T>(_name: string, fn: () => Promise<T>): Promise<T>;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export { AgentLens, type AgentLensConfig, LLM_REGISTRY, type OutboundSpan, type ParsedSpan, matchLLM, scrubObject, scrubPII };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
export { getCurrentSpanId, getCurrentTrace, getCurrentTraceId, runWithTrace } from '@farzanhossans/agentlens-core';
|
|
2
|
+
|
|
3
|
+
type ParserName = 'openai' | 'anthropic' | 'gemini' | 'cohere' | 'mistral';
|
|
4
|
+
interface LLMEndpoint {
|
|
5
|
+
provider: string;
|
|
6
|
+
parser: ParserName;
|
|
7
|
+
paths: string[];
|
|
8
|
+
}
|
|
9
|
+
interface ParsedSpan {
|
|
10
|
+
model: string;
|
|
11
|
+
provider: string;
|
|
12
|
+
inputTokens: number;
|
|
13
|
+
outputTokens: number;
|
|
14
|
+
totalTokens: number;
|
|
15
|
+
costUsd: number;
|
|
16
|
+
inputText: string;
|
|
17
|
+
outputText: string;
|
|
18
|
+
isStream: boolean;
|
|
19
|
+
error?: string;
|
|
20
|
+
}
|
|
21
|
+
interface AgentLensConfig {
|
|
22
|
+
apiKey: string;
|
|
23
|
+
endpoint?: string;
|
|
24
|
+
debug?: boolean;
|
|
25
|
+
pii?: boolean;
|
|
26
|
+
flushIntervalMs?: number;
|
|
27
|
+
maxBatchSize?: number;
|
|
28
|
+
}
|
|
29
|
+
interface OutboundSpan extends ParsedSpan {
|
|
30
|
+
spanId: string;
|
|
31
|
+
traceId: string;
|
|
32
|
+
parentSpanId?: string;
|
|
33
|
+
timestamp: string;
|
|
34
|
+
latency: number;
|
|
35
|
+
status: number;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
declare const LLM_REGISTRY: Record<string, LLMEndpoint>;
|
|
39
|
+
declare function matchLLM(url: string): LLMEndpoint | null;
|
|
40
|
+
|
|
41
|
+
declare function scrubPII(text: string): string;
|
|
42
|
+
declare function scrubObject(obj: unknown): unknown;
|
|
43
|
+
|
|
44
|
+
declare const AgentLens: {
|
|
45
|
+
init(config: AgentLensConfig): void;
|
|
46
|
+
flush(): Promise<void>;
|
|
47
|
+
shutdown(): void;
|
|
48
|
+
/**
|
|
49
|
+
* Wraps `fn` in a named trace context. Any LLM calls made inside `fn`
|
|
50
|
+
* (including async ones) are auto-tagged with this trace's `traceId` and
|
|
51
|
+
* get `parentSpanId` set to this trace's `spanId`. Nested `trace()` calls
|
|
52
|
+
* become child spans automatically.
|
|
53
|
+
*
|
|
54
|
+
* Use this to group multiple LLM calls that belong to the same agent run.
|
|
55
|
+
*/
|
|
56
|
+
trace<T>(_name: string, fn: () => Promise<T>): Promise<T>;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export { AgentLens, type AgentLensConfig, LLM_REGISTRY, type OutboundSpan, type ParsedSpan, matchLLM, scrubObject, scrubPII };
|