@lelemondev/sdk 0.3.0 → 0.5.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 +536 -93
- package/dist/express-Dt5wT6_n.d.mts +67 -0
- package/dist/express-Dt5wT6_n.d.ts +67 -0
- package/dist/express.d.mts +1 -0
- package/dist/express.d.ts +1 -0
- package/dist/express.js +21 -0
- package/dist/express.js.map +1 -0
- package/dist/express.mjs +19 -0
- package/dist/express.mjs.map +1 -0
- package/dist/hono-Dzmu77iW.d.mts +80 -0
- package/dist/hono-Dzmu77iW.d.ts +80 -0
- package/dist/hono.d.mts +1 -0
- package/dist/hono.d.ts +1 -0
- package/dist/hono.js +23 -0
- package/dist/hono.js.map +1 -0
- package/dist/hono.mjs +21 -0
- package/dist/hono.mjs.map +1 -0
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +949 -3
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +949 -3
- package/dist/index.mjs.map +1 -1
- package/dist/integrations.d.mts +4 -0
- package/dist/integrations.d.ts +4 -0
- package/dist/integrations.js +93 -0
- package/dist/integrations.js.map +1 -0
- package/dist/integrations.mjs +88 -0
- package/dist/integrations.mjs.map +1 -0
- package/dist/lambda-CAuiF9dH.d.mts +79 -0
- package/dist/lambda-CAuiF9dH.d.ts +79 -0
- package/dist/lambda.d.mts +1 -0
- package/dist/lambda.d.ts +1 -0
- package/dist/lambda.js +21 -0
- package/dist/lambda.js.map +1 -0
- package/dist/lambda.mjs +19 -0
- package/dist/lambda.mjs.map +1 -0
- package/dist/next-BC9PmEho.d.mts +100 -0
- package/dist/next-BC9PmEho.d.ts +100 -0
- package/dist/next.d.mts +1 -0
- package/dist/next.d.ts +1 -0
- package/dist/next.js +33 -0
- package/dist/next.js.map +1 -0
- package/dist/next.mjs +30 -0
- package/dist/next.mjs.map +1 -0
- package/package.json +59 -11
package/README.md
CHANGED
|
@@ -4,16 +4,22 @@
|
|
|
4
4
|
[](https://github.com/lelemondev/lelemondev-sdk/actions/workflows/ci.yml)
|
|
5
5
|
[](https://opensource.org/licenses/MIT)
|
|
6
6
|
|
|
7
|
-
Automatic LLM observability for Node.js.
|
|
7
|
+
Automatic LLM observability for Node.js. Wrap your client, everything is traced.
|
|
8
|
+
|
|
9
|
+
## LLM Agent Docs
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
https://lelemondev.github.io/lelemondev-sdk/llms.txt
|
|
13
|
+
https://lelemondev.github.io/lelemondev-sdk/llms-full.txt
|
|
14
|
+
```
|
|
8
15
|
|
|
9
16
|
## Features
|
|
10
17
|
|
|
11
|
-
- **Automatic Tracing** - Wrap your client,
|
|
12
|
-
- **
|
|
13
|
-
- **
|
|
18
|
+
- **Automatic Tracing** - Wrap your client, all calls are traced
|
|
19
|
+
- **Zero Config** - Works out of the box
|
|
20
|
+
- **Framework Integrations** - Next.js, Express, Lambda, Hono
|
|
14
21
|
- **Streaming Support** - Full support for streaming responses
|
|
15
22
|
- **Type-safe** - Preserves your client's TypeScript types
|
|
16
|
-
- **Serverless-ready** - Built-in flush for Lambda/Vercel
|
|
17
23
|
|
|
18
24
|
## Installation
|
|
19
25
|
|
|
@@ -24,33 +30,262 @@ npm install @lelemondev/sdk
|
|
|
24
30
|
## Quick Start
|
|
25
31
|
|
|
26
32
|
```typescript
|
|
27
|
-
import { init, observe
|
|
33
|
+
import { init, observe } from '@lelemondev/sdk';
|
|
28
34
|
import OpenAI from 'openai';
|
|
29
35
|
|
|
30
|
-
// 1. Initialize once
|
|
36
|
+
// 1. Initialize once
|
|
31
37
|
init({ apiKey: process.env.LELEMON_API_KEY });
|
|
32
38
|
|
|
33
39
|
// 2. Wrap your client
|
|
34
40
|
const openai = observe(new OpenAI());
|
|
35
41
|
|
|
36
|
-
// 3. Use normally - all calls
|
|
42
|
+
// 3. Use normally - all calls traced automatically
|
|
37
43
|
const response = await openai.chat.completions.create({
|
|
38
44
|
model: 'gpt-4',
|
|
39
45
|
messages: [{ role: 'user', content: 'Hello!' }],
|
|
40
46
|
});
|
|
41
|
-
|
|
42
|
-
// 4. For serverless: flush before response
|
|
43
|
-
await flush();
|
|
44
47
|
```
|
|
45
48
|
|
|
46
|
-
That's it! No manual tracing code needed.
|
|
47
|
-
|
|
48
49
|
## Supported Providers
|
|
49
50
|
|
|
50
51
|
| Provider | Status | Methods |
|
|
51
52
|
|----------|--------|---------|
|
|
52
|
-
| OpenAI |
|
|
53
|
-
|
|
|
53
|
+
| OpenAI | ✅ | `chat.completions.create()`, `responses.create()`, `completions.create()`, `embeddings.create()` |
|
|
54
|
+
| OpenRouter | ✅ | `chat.completions.create()` (access to 400+ models) |
|
|
55
|
+
| Anthropic | ✅ | `messages.create()`, `messages.stream()` |
|
|
56
|
+
| AWS Bedrock | ✅ | `ConverseCommand`, `ConverseStreamCommand`, `InvokeModelCommand` |
|
|
57
|
+
| Google Gemini | ✅ | `generateContent()`, `generateContentStream()`, `chat.sendMessage()` |
|
|
58
|
+
|
|
59
|
+
### OpenRouter
|
|
60
|
+
|
|
61
|
+
[OpenRouter](https://openrouter.ai) provides unified access to 400+ models from OpenAI, Anthropic, Google, Meta, Mistral, and more through a single API.
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
import { init, observe } from '@lelemondev/sdk';
|
|
65
|
+
import OpenAI from 'openai';
|
|
66
|
+
|
|
67
|
+
init({ apiKey: process.env.LELEMON_API_KEY });
|
|
68
|
+
|
|
69
|
+
// Configure OpenAI SDK to use OpenRouter
|
|
70
|
+
const openrouter = observe(new OpenAI({
|
|
71
|
+
baseURL: 'https://openrouter.ai/api/v1',
|
|
72
|
+
apiKey: process.env.OPENROUTER_API_KEY,
|
|
73
|
+
defaultHeaders: {
|
|
74
|
+
'HTTP-Referer': 'https://your-app.com', // Optional: for OpenRouter rankings
|
|
75
|
+
'X-Title': 'Your App Name', // Optional: for OpenRouter rankings
|
|
76
|
+
},
|
|
77
|
+
}));
|
|
78
|
+
|
|
79
|
+
// Access any model through OpenRouter
|
|
80
|
+
const response = await openrouter.chat.completions.create({
|
|
81
|
+
model: 'anthropic/claude-3-opus', // or 'openai/gpt-4', 'google/gemini-pro', etc.
|
|
82
|
+
messages: [{ role: 'user', content: 'Hello!' }],
|
|
83
|
+
});
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Models are specified in `provider/model` format (e.g., `anthropic/claude-3-opus`, `openai/gpt-4`, `meta-llama/llama-3-70b`). See [OpenRouter Models](https://openrouter.ai/models) for the full list.
|
|
87
|
+
|
|
88
|
+
## Usage Without Framework Integrations
|
|
89
|
+
|
|
90
|
+
The SDK works with **any Node.js application**. Framework integrations are optional - they just automate the `flush()` call.
|
|
91
|
+
|
|
92
|
+
### Long-running Processes (Servers, Workers)
|
|
93
|
+
|
|
94
|
+
For long-running processes, the SDK auto-flushes every second (configurable via `flushIntervalMs`):
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
import { init, observe } from '@lelemondev/sdk';
|
|
98
|
+
import OpenAI from 'openai';
|
|
99
|
+
|
|
100
|
+
init({ apiKey: process.env.LELEMON_API_KEY });
|
|
101
|
+
const openai = observe(new OpenAI());
|
|
102
|
+
|
|
103
|
+
// In your HTTP server, WebSocket handler, queue worker, etc.
|
|
104
|
+
async function handleRequest(userId: string, message: string) {
|
|
105
|
+
const client = observe(new OpenAI(), { userId });
|
|
106
|
+
|
|
107
|
+
const result = await client.chat.completions.create({
|
|
108
|
+
model: 'gpt-4',
|
|
109
|
+
messages: [{ role: 'user', content: message }],
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
return result.choices[0].message;
|
|
113
|
+
// Traces are auto-flushed in the background (every 1s by default)
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Short-lived Processes (Scripts, Serverless, CLI)
|
|
118
|
+
|
|
119
|
+
For scripts or serverless functions, call `flush()` before the process exits:
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
import { init, observe, flush } from '@lelemondev/sdk';
|
|
123
|
+
import OpenAI from 'openai';
|
|
124
|
+
|
|
125
|
+
init({ apiKey: process.env.LELEMON_API_KEY });
|
|
126
|
+
const openai = observe(new OpenAI());
|
|
127
|
+
|
|
128
|
+
async function main() {
|
|
129
|
+
const result = await openai.chat.completions.create({
|
|
130
|
+
model: 'gpt-4',
|
|
131
|
+
messages: [{ role: 'user', content: 'Hello!' }],
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
console.log(result.choices[0].message.content);
|
|
135
|
+
|
|
136
|
+
// IMPORTANT: Flush before exit to ensure traces are sent
|
|
137
|
+
await flush();
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
main();
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Custom HTTP Server (No Framework)
|
|
144
|
+
|
|
145
|
+
```typescript
|
|
146
|
+
import http from 'http';
|
|
147
|
+
import { init, observe, flush } from '@lelemondev/sdk';
|
|
148
|
+
import OpenAI from 'openai';
|
|
149
|
+
|
|
150
|
+
init({ apiKey: process.env.LELEMON_API_KEY });
|
|
151
|
+
|
|
152
|
+
const server = http.createServer(async (req, res) => {
|
|
153
|
+
if (req.method === 'POST' && req.url === '/chat') {
|
|
154
|
+
const openai = observe(new OpenAI(), {
|
|
155
|
+
userId: req.headers['x-user-id'] as string,
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
const result = await openai.chat.completions.create({
|
|
159
|
+
model: 'gpt-4',
|
|
160
|
+
messages: [{ role: 'user', content: 'Hello!' }],
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
164
|
+
res.end(JSON.stringify(result.choices[0].message));
|
|
165
|
+
// Auto-flush handles this in background
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// Graceful shutdown - flush remaining traces
|
|
170
|
+
process.on('SIGTERM', async () => {
|
|
171
|
+
await flush();
|
|
172
|
+
server.close();
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
server.listen(3000);
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### When to Use Framework Integrations vs Manual
|
|
179
|
+
|
|
180
|
+
| Scenario | Recommendation |
|
|
181
|
+
|----------|----------------|
|
|
182
|
+
| Next.js, Express, Hono, Lambda | Use framework integration (auto-flush) |
|
|
183
|
+
| Custom HTTP server | Auto-flush works, add `flush()` on shutdown |
|
|
184
|
+
| CLI scripts | Always call `flush()` before exit |
|
|
185
|
+
| Background workers (Bull, BullMQ) | Auto-flush works, add `flush()` on shutdown |
|
|
186
|
+
| One-off scripts | Always call `flush()` before exit |
|
|
187
|
+
| Long-running daemons | Auto-flush works |
|
|
188
|
+
|
|
189
|
+
## Framework Integrations
|
|
190
|
+
|
|
191
|
+
Framework integrations automate the `flush()` call so you don't have to think about it.
|
|
192
|
+
|
|
193
|
+
### Next.js App Router
|
|
194
|
+
|
|
195
|
+
```typescript
|
|
196
|
+
// app/api/chat/route.ts
|
|
197
|
+
import { init, observe } from '@lelemondev/sdk';
|
|
198
|
+
import { withObserve } from '@lelemondev/sdk/next';
|
|
199
|
+
import { after } from 'next/server';
|
|
200
|
+
import OpenAI from 'openai';
|
|
201
|
+
|
|
202
|
+
init({ apiKey: process.env.LELEMON_API_KEY });
|
|
203
|
+
const openai = observe(new OpenAI());
|
|
204
|
+
|
|
205
|
+
export const POST = withObserve(
|
|
206
|
+
async (req) => {
|
|
207
|
+
const { message } = await req.json();
|
|
208
|
+
const result = await openai.chat.completions.create({
|
|
209
|
+
model: 'gpt-4',
|
|
210
|
+
messages: [{ role: 'user', content: message }],
|
|
211
|
+
});
|
|
212
|
+
return Response.json(result.choices[0].message);
|
|
213
|
+
},
|
|
214
|
+
{ after } // Non-blocking flush (Next.js 15+)
|
|
215
|
+
);
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### Express
|
|
219
|
+
|
|
220
|
+
```typescript
|
|
221
|
+
import express from 'express';
|
|
222
|
+
import { init, observe } from '@lelemondev/sdk';
|
|
223
|
+
import { createMiddleware } from '@lelemondev/sdk/express';
|
|
224
|
+
import OpenAI from 'openai';
|
|
225
|
+
|
|
226
|
+
init({ apiKey: process.env.LELEMON_API_KEY });
|
|
227
|
+
const openai = observe(new OpenAI());
|
|
228
|
+
|
|
229
|
+
const app = express();
|
|
230
|
+
app.use(createMiddleware()); // Auto-flush on response finish
|
|
231
|
+
|
|
232
|
+
app.post('/chat', async (req, res) => {
|
|
233
|
+
const result = await openai.chat.completions.create({
|
|
234
|
+
model: 'gpt-4',
|
|
235
|
+
messages: [{ role: 'user', content: req.body.message }],
|
|
236
|
+
});
|
|
237
|
+
res.json(result.choices[0].message);
|
|
238
|
+
});
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### AWS Lambda
|
|
242
|
+
|
|
243
|
+
```typescript
|
|
244
|
+
import { init, observe } from '@lelemondev/sdk';
|
|
245
|
+
import { withObserve } from '@lelemondev/sdk/lambda';
|
|
246
|
+
import OpenAI from 'openai';
|
|
247
|
+
|
|
248
|
+
init({ apiKey: process.env.LELEMON_API_KEY });
|
|
249
|
+
const openai = observe(new OpenAI());
|
|
250
|
+
|
|
251
|
+
export const handler = withObserve(async (event) => {
|
|
252
|
+
const body = JSON.parse(event.body);
|
|
253
|
+
const result = await openai.chat.completions.create({
|
|
254
|
+
model: 'gpt-4',
|
|
255
|
+
messages: [{ role: 'user', content: body.message }],
|
|
256
|
+
});
|
|
257
|
+
return {
|
|
258
|
+
statusCode: 200,
|
|
259
|
+
body: JSON.stringify(result.choices[0].message),
|
|
260
|
+
};
|
|
261
|
+
});
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
### Hono (Cloudflare Workers, Deno, Bun)
|
|
265
|
+
|
|
266
|
+
```typescript
|
|
267
|
+
import { Hono } from 'hono';
|
|
268
|
+
import { init, observe } from '@lelemondev/sdk';
|
|
269
|
+
import { createMiddleware } from '@lelemondev/sdk/hono';
|
|
270
|
+
import OpenAI from 'openai';
|
|
271
|
+
|
|
272
|
+
init({ apiKey: process.env.LELEMON_API_KEY });
|
|
273
|
+
const openai = observe(new OpenAI());
|
|
274
|
+
|
|
275
|
+
const app = new Hono();
|
|
276
|
+
app.use(createMiddleware()); // Uses waitUntil on Workers
|
|
277
|
+
|
|
278
|
+
app.post('/chat', async (c) => {
|
|
279
|
+
const { message } = await c.req.json();
|
|
280
|
+
const result = await openai.chat.completions.create({
|
|
281
|
+
model: 'gpt-4',
|
|
282
|
+
messages: [{ role: 'user', content: message }],
|
|
283
|
+
});
|
|
284
|
+
return c.json(result.choices[0].message);
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
export default app;
|
|
288
|
+
```
|
|
54
289
|
|
|
55
290
|
## API Reference
|
|
56
291
|
|
|
@@ -60,147 +295,348 @@ Initialize the SDK. Call once at app startup.
|
|
|
60
295
|
|
|
61
296
|
```typescript
|
|
62
297
|
init({
|
|
63
|
-
apiKey: 'le_xxx', // Required (or
|
|
298
|
+
apiKey: 'le_xxx', // Required (or LELEMON_API_KEY env var)
|
|
64
299
|
endpoint: 'https://...', // Optional, custom endpoint
|
|
65
300
|
debug: false, // Optional, enable debug logs
|
|
66
|
-
disabled: false, // Optional, disable
|
|
301
|
+
disabled: false, // Optional, disable tracing
|
|
67
302
|
batchSize: 10, // Optional, items per batch
|
|
68
303
|
flushIntervalMs: 1000, // Optional, auto-flush interval
|
|
69
|
-
requestTimeoutMs: 10000, // Optional, HTTP request timeout
|
|
70
304
|
});
|
|
71
305
|
```
|
|
72
306
|
|
|
73
307
|
### `observe(client, options?)`
|
|
74
308
|
|
|
75
|
-
Wrap an LLM client with automatic tracing.
|
|
309
|
+
Wrap an LLM client with automatic tracing.
|
|
76
310
|
|
|
77
311
|
```typescript
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
const anthropic = observe(new Anthropic(), {
|
|
81
|
-
sessionId: 'session-123', // Optional, group related traces
|
|
82
|
-
userId: 'user-456', // Optional, identify the user
|
|
83
|
-
metadata: { source: 'api' }, // Optional, custom metadata
|
|
84
|
-
tags: ['production'], // Optional, tags for filtering
|
|
85
|
-
});
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
### `createObserve(defaultOptions)`
|
|
89
|
-
|
|
90
|
-
Create a scoped observe function with preset options.
|
|
91
|
-
|
|
92
|
-
```typescript
|
|
93
|
-
const observeWithSession = createObserve({
|
|
312
|
+
const openai = observe(new OpenAI(), {
|
|
94
313
|
sessionId: 'session-123',
|
|
95
314
|
userId: 'user-456',
|
|
315
|
+
metadata: { source: 'api' },
|
|
316
|
+
tags: ['production'],
|
|
96
317
|
});
|
|
97
|
-
|
|
98
|
-
const openai = observeWithSession(new OpenAI());
|
|
99
|
-
const anthropic = observeWithSession(new Anthropic());
|
|
100
318
|
```
|
|
101
319
|
|
|
102
320
|
### `flush()`
|
|
103
321
|
|
|
104
|
-
|
|
322
|
+
Manually flush pending traces. Use in serverless without framework integration.
|
|
105
323
|
|
|
106
324
|
```typescript
|
|
107
325
|
await flush();
|
|
108
326
|
```
|
|
109
327
|
|
|
110
|
-
|
|
328
|
+
## User & Session Tracking
|
|
111
329
|
|
|
112
|
-
|
|
330
|
+
Track which user generated each trace and group related conversations.
|
|
113
331
|
|
|
332
|
+
### Available Fields
|
|
333
|
+
|
|
334
|
+
| Field | Purpose | In Dashboard |
|
|
335
|
+
|-------|---------|--------------|
|
|
336
|
+
| `userId` | Identify the end user | Shown in "User" column, searchable |
|
|
337
|
+
| `sessionId` | Group related traces (e.g., a conversation) | Shown in "Session" column, searchable |
|
|
338
|
+
| `metadata` | Custom data attached to the trace | Visible in trace detail view |
|
|
339
|
+
| `tags` | Labels for categorization | Shown as badges, filterable dropdown |
|
|
340
|
+
|
|
341
|
+
#### Field Details
|
|
342
|
+
|
|
343
|
+
**`userId`** - Identifies who made the request
|
|
344
|
+
```typescript
|
|
345
|
+
userId: req.user.id // From your auth system
|
|
346
|
+
userId: 'user-123' // Any string identifier
|
|
347
|
+
userId: req.user.email // Email works too
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
**`sessionId`** - Groups multiple calls into one conversation/session
|
|
114
351
|
```typescript
|
|
115
|
-
|
|
116
|
-
|
|
352
|
+
sessionId: req.body.conversationId // Chat conversation ID
|
|
353
|
+
sessionId: req.headers['x-session-id'] // From client header
|
|
354
|
+
sessionId: crypto.randomUUID() // Generate per session
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
**`metadata`** - Any extra data you want to attach (stored as JSON)
|
|
358
|
+
```typescript
|
|
359
|
+
metadata: {
|
|
360
|
+
plan: 'premium', // User's subscription plan
|
|
361
|
+
feature: 'chat', // Which feature triggered this
|
|
362
|
+
version: '2.1.0', // Your app version
|
|
363
|
+
environment: 'production', // Environment
|
|
364
|
+
ip: req.ip, // Client IP (for debugging)
|
|
365
|
+
customField: 'any value', // Anything you need
|
|
117
366
|
}
|
|
118
367
|
```
|
|
119
368
|
|
|
120
|
-
|
|
369
|
+
**`tags`** - Quick labels for filtering (array of strings)
|
|
370
|
+
```typescript
|
|
371
|
+
tags: ['production'] // Environment
|
|
372
|
+
tags: ['chat', 'premium'] // Feature + plan
|
|
373
|
+
tags: ['api', 'v2'] // Service + version
|
|
374
|
+
tags: [tenantId, 'high-priority'] // Multi-tenant + priority
|
|
375
|
+
```
|
|
121
376
|
|
|
122
|
-
|
|
377
|
+
### Basic Usage
|
|
123
378
|
|
|
124
379
|
```typescript
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
messages: [{ role: 'user', content: 'Hello!' }],
|
|
129
|
-
stream: true,
|
|
380
|
+
const openai = observe(new OpenAI(), {
|
|
381
|
+
userId: 'user-123',
|
|
382
|
+
sessionId: 'conversation-abc',
|
|
130
383
|
});
|
|
384
|
+
```
|
|
131
385
|
|
|
132
|
-
|
|
133
|
-
process.stdout.write(chunk.choices[0]?.delta?.content || '');
|
|
134
|
-
}
|
|
135
|
-
// Trace is captured automatically when stream completes
|
|
386
|
+
### In an API Endpoint
|
|
136
387
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
388
|
+
```typescript
|
|
389
|
+
// Express
|
|
390
|
+
app.post('/chat', async (req, res) => {
|
|
391
|
+
// Create client with user context from the request
|
|
392
|
+
const openai = observe(new OpenAI(), {
|
|
393
|
+
userId: req.user.id,
|
|
394
|
+
sessionId: req.headers['x-session-id'],
|
|
395
|
+
metadata: {
|
|
396
|
+
plan: req.user.plan,
|
|
397
|
+
endpoint: '/chat'
|
|
398
|
+
},
|
|
399
|
+
});
|
|
143
400
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
}
|
|
147
|
-
|
|
401
|
+
const result = await openai.chat.completions.create({
|
|
402
|
+
model: 'gpt-4',
|
|
403
|
+
messages: [{ role: 'user', content: req.body.message }],
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
res.json(result.choices[0].message);
|
|
407
|
+
});
|
|
148
408
|
```
|
|
149
409
|
|
|
150
|
-
|
|
410
|
+
### Reusable Context with createObserve()
|
|
151
411
|
|
|
152
|
-
|
|
412
|
+
When you have multiple LLM clients or make calls from different places, use `createObserve()` to avoid repeating context:
|
|
153
413
|
|
|
154
414
|
```typescript
|
|
155
|
-
import {
|
|
156
|
-
import { init, observe, flush } from '@lelemondev/sdk';
|
|
415
|
+
import { createObserve } from '@lelemondev/sdk';
|
|
157
416
|
import OpenAI from 'openai';
|
|
417
|
+
import { GoogleGenerativeAI } from '@google/generative-ai';
|
|
418
|
+
import { BedrockRuntimeClient } from '@aws-sdk/client-bedrock-runtime';
|
|
419
|
+
|
|
420
|
+
// Create a scoped observe function with user context
|
|
421
|
+
const observeForUser = createObserve({
|
|
422
|
+
userId: 'user-123',
|
|
423
|
+
sessionId: 'session-456',
|
|
424
|
+
tags: ['premium'],
|
|
425
|
+
});
|
|
158
426
|
|
|
159
|
-
|
|
160
|
-
const openai =
|
|
427
|
+
// All clients inherit the same context
|
|
428
|
+
const openai = observeForUser(new OpenAI());
|
|
429
|
+
const gemini = observeForUser(new GoogleGenerativeAI(apiKey));
|
|
430
|
+
const bedrock = observeForUser(new BedrockRuntimeClient({}));
|
|
161
431
|
|
|
162
|
-
|
|
163
|
-
|
|
432
|
+
// All these calls will be associated with user-123
|
|
433
|
+
await openai.chat.completions.create({ ... });
|
|
434
|
+
await gemini.getGenerativeModel({ model: 'gemini-pro' }).generateContent('...');
|
|
435
|
+
```
|
|
164
436
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
437
|
+
### In Middleware (Express)
|
|
438
|
+
|
|
439
|
+
Set up user context once, use it everywhere:
|
|
440
|
+
|
|
441
|
+
```typescript
|
|
442
|
+
import { createObserve } from '@lelemondev/sdk';
|
|
443
|
+
|
|
444
|
+
// Middleware to attach observe function to request
|
|
445
|
+
app.use((req, res, next) => {
|
|
446
|
+
req.observe = createObserve({
|
|
447
|
+
userId: req.user?.id,
|
|
448
|
+
sessionId: req.headers['x-session-id'] || crypto.randomUUID(),
|
|
449
|
+
metadata: {
|
|
450
|
+
ip: req.ip,
|
|
451
|
+
userAgent: req.headers['user-agent'],
|
|
452
|
+
},
|
|
168
453
|
});
|
|
454
|
+
next();
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
// In any route handler
|
|
458
|
+
app.post('/chat', async (req, res) => {
|
|
459
|
+
const openai = req.observe(new OpenAI());
|
|
460
|
+
// Calls are automatically associated with the user
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
app.post('/summarize', async (req, res) => {
|
|
464
|
+
const gemini = req.observe(new GoogleGenerativeAI(apiKey));
|
|
465
|
+
// Same user context, different endpoint
|
|
466
|
+
});
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
### Multi-tenant Application
|
|
470
|
+
|
|
471
|
+
```typescript
|
|
472
|
+
app.post('/api/:tenantId/chat', async (req, res) => {
|
|
473
|
+
const openai = observe(new OpenAI(), {
|
|
474
|
+
userId: req.user.id,
|
|
475
|
+
sessionId: req.body.conversationId,
|
|
476
|
+
metadata: {
|
|
477
|
+
tenantId: req.params.tenantId,
|
|
478
|
+
environment: process.env.NODE_ENV,
|
|
479
|
+
},
|
|
480
|
+
tags: [req.params.tenantId, req.user.plan],
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
// Traces can be filtered by tenant, user, or conversation
|
|
484
|
+
});
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
### Distributed Systems (API + WebSocket)
|
|
488
|
+
|
|
489
|
+
When your application spans multiple services (REST API, WebSocket server, background workers), use consistent identifiers across all of them:
|
|
169
490
|
|
|
170
|
-
|
|
171
|
-
|
|
491
|
+
```typescript
|
|
492
|
+
// Shared utility for creating observe context
|
|
493
|
+
// utils/observe-context.ts
|
|
494
|
+
import { createObserve } from '@lelemondev/sdk';
|
|
495
|
+
|
|
496
|
+
export function createUserObserve(userId: string, sessionId: string, service: string) {
|
|
497
|
+
return createObserve({
|
|
498
|
+
userId,
|
|
499
|
+
sessionId,
|
|
500
|
+
metadata: { service },
|
|
501
|
+
tags: [service],
|
|
502
|
+
});
|
|
172
503
|
}
|
|
173
504
|
```
|
|
174
505
|
|
|
175
|
-
|
|
506
|
+
**REST API Server:**
|
|
176
507
|
|
|
177
508
|
```typescript
|
|
178
|
-
|
|
179
|
-
import
|
|
509
|
+
// api-server.ts
|
|
510
|
+
import { createUserObserve } from './utils/observe-context';
|
|
180
511
|
|
|
181
|
-
|
|
182
|
-
const
|
|
512
|
+
app.post('/chat/start', async (req, res) => {
|
|
513
|
+
const { userId, sessionId } = req.body;
|
|
514
|
+
|
|
515
|
+
const observe = createUserObserve(userId, sessionId, 'api');
|
|
516
|
+
const openai = observe(new OpenAI());
|
|
183
517
|
|
|
184
|
-
|
|
185
|
-
const response = await openai.chat.completions.create({
|
|
518
|
+
const result = await openai.chat.completions.create({
|
|
186
519
|
model: 'gpt-4',
|
|
187
|
-
messages: [{ role: 'user', content:
|
|
520
|
+
messages: [{ role: 'user', content: req.body.message }],
|
|
521
|
+
});
|
|
522
|
+
|
|
523
|
+
res.json({
|
|
524
|
+
response: result.choices[0].message,
|
|
525
|
+
sessionId, // Return sessionId for client to use in WebSocket
|
|
526
|
+
});
|
|
527
|
+
});
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
**WebSocket Server:**
|
|
531
|
+
|
|
532
|
+
```typescript
|
|
533
|
+
// ws-server.ts
|
|
534
|
+
import { createUserObserve } from './utils/observe-context';
|
|
535
|
+
|
|
536
|
+
wss.on('connection', (ws, req) => {
|
|
537
|
+
// Get userId and sessionId from connection (query params, auth token, etc.)
|
|
538
|
+
const userId = getUserFromToken(req);
|
|
539
|
+
const sessionId = new URL(req.url, 'http://localhost').searchParams.get('sessionId');
|
|
540
|
+
|
|
541
|
+
// Create observe function for this connection
|
|
542
|
+
const observe = createUserObserve(userId, sessionId, 'websocket');
|
|
543
|
+
const gemini = observe(new GoogleGenerativeAI(apiKey));
|
|
544
|
+
|
|
545
|
+
ws.on('message', async (data) => {
|
|
546
|
+
const { message } = JSON.parse(data);
|
|
547
|
+
|
|
548
|
+
// This trace will be linked to the same session as the API calls
|
|
549
|
+
const model = gemini.getGenerativeModel({ model: 'gemini-pro' });
|
|
550
|
+
const result = await model.generateContent(message);
|
|
551
|
+
|
|
552
|
+
ws.send(JSON.stringify({ response: result.response.text() }));
|
|
553
|
+
});
|
|
554
|
+
});
|
|
555
|
+
```
|
|
556
|
+
|
|
557
|
+
**Background Worker / Queue Consumer:**
|
|
558
|
+
|
|
559
|
+
```typescript
|
|
560
|
+
// worker.ts
|
|
561
|
+
import { createUserObserve } from './utils/observe-context';
|
|
562
|
+
|
|
563
|
+
queue.process('ai-task', async (job) => {
|
|
564
|
+
const { userId, sessionId, prompt } = job.data;
|
|
565
|
+
|
|
566
|
+
const observe = createUserObserve(userId, sessionId, 'worker');
|
|
567
|
+
const bedrock = observe(new BedrockRuntimeClient({}));
|
|
568
|
+
|
|
569
|
+
const command = new ConverseCommand({
|
|
570
|
+
modelId: 'anthropic.claude-3-sonnet-20240229-v1:0',
|
|
571
|
+
messages: [{ role: 'user', content: [{ text: prompt }] }],
|
|
188
572
|
});
|
|
189
573
|
|
|
190
|
-
await
|
|
191
|
-
return
|
|
192
|
-
};
|
|
574
|
+
const result = await bedrock.send(command);
|
|
575
|
+
return result.output.message.content[0].text;
|
|
576
|
+
});
|
|
577
|
+
```
|
|
578
|
+
|
|
579
|
+
**Client-side (passing sessionId between services):**
|
|
580
|
+
|
|
581
|
+
```typescript
|
|
582
|
+
// Frontend - maintains sessionId across API and WebSocket
|
|
583
|
+
const sessionId = crypto.randomUUID(); // Or from your session management
|
|
584
|
+
|
|
585
|
+
// REST API call
|
|
586
|
+
const response = await fetch('/chat/start', {
|
|
587
|
+
method: 'POST',
|
|
588
|
+
body: JSON.stringify({ userId, sessionId, message: 'Hello' }),
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
// WebSocket connection with same sessionId
|
|
592
|
+
const ws = new WebSocket(`wss://your-app.com/ws?sessionId=${sessionId}`);
|
|
593
|
+
|
|
594
|
+
// Both API and WebSocket traces will be grouped under the same session
|
|
595
|
+
```
|
|
596
|
+
|
|
597
|
+
**Viewing in Dashboard:**
|
|
598
|
+
|
|
599
|
+
With consistent `userId` and `sessionId` across services, you can:
|
|
600
|
+
- See a user's complete journey across API → WebSocket → Background jobs
|
|
601
|
+
- Filter by service (`metadata.service`) to isolate issues
|
|
602
|
+
- Track a conversation that spans multiple services
|
|
603
|
+
|
|
604
|
+
### What This Enables
|
|
605
|
+
|
|
606
|
+
With proper user/session tracking you can:
|
|
607
|
+
|
|
608
|
+
- **Filter traces by user** - See all LLM calls from a specific user
|
|
609
|
+
- **View full conversations** - Group all calls in a session together
|
|
610
|
+
- **Debug issues** - Find exactly what happened for a specific user request
|
|
611
|
+
- **Analyze usage patterns** - Understand how different user segments use your AI features
|
|
612
|
+
- **Cost attribution** - Track token usage per user, tenant, or feature
|
|
613
|
+
|
|
614
|
+
## Streaming
|
|
615
|
+
|
|
616
|
+
All providers support streaming:
|
|
617
|
+
|
|
618
|
+
```typescript
|
|
619
|
+
const stream = await openai.chat.completions.create({
|
|
620
|
+
model: 'gpt-4',
|
|
621
|
+
messages: [{ role: 'user', content: 'Hello!' }],
|
|
622
|
+
stream: true,
|
|
623
|
+
});
|
|
624
|
+
|
|
625
|
+
for await (const chunk of stream) {
|
|
626
|
+
process.stdout.write(chunk.choices[0]?.delta?.content || '');
|
|
627
|
+
}
|
|
628
|
+
// Trace captured automatically when stream completes
|
|
193
629
|
```
|
|
194
630
|
|
|
195
631
|
## What Gets Traced
|
|
196
632
|
|
|
197
633
|
Each LLM call automatically captures:
|
|
198
634
|
|
|
199
|
-
- **Provider** - openai, anthropic
|
|
200
|
-
- **Model** - gpt-4, claude-3-opus, etc.
|
|
635
|
+
- **Provider** - openai, anthropic, bedrock, gemini
|
|
636
|
+
- **Model** - gpt-4, claude-3-opus, gemini-pro, etc.
|
|
201
637
|
- **Input** - Messages/prompt (sanitized)
|
|
202
638
|
- **Output** - Response content
|
|
203
|
-
- **Tokens** - Input and output
|
|
639
|
+
- **Tokens** - Input and output counts
|
|
204
640
|
- **Duration** - Request latency in ms
|
|
205
641
|
- **Status** - success or error
|
|
206
642
|
- **Streaming** - Whether streaming was used
|
|
@@ -211,14 +647,21 @@ The SDK automatically sanitizes sensitive data:
|
|
|
211
647
|
|
|
212
648
|
- API keys and tokens are redacted
|
|
213
649
|
- Large payloads are truncated
|
|
214
|
-
- Errors are captured
|
|
650
|
+
- Errors are captured safely
|
|
215
651
|
|
|
216
652
|
## Environment Variables
|
|
217
653
|
|
|
218
654
|
| Variable | Description |
|
|
219
655
|
|----------|-------------|
|
|
220
|
-
| `LELEMON_API_KEY` | Your
|
|
656
|
+
| `LELEMON_API_KEY` | Your API key (starts with `le_`) |
|
|
221
657
|
|
|
222
658
|
## License
|
|
223
659
|
|
|
224
660
|
MIT
|
|
661
|
+
|
|
662
|
+
## Sources
|
|
663
|
+
|
|
664
|
+
Framework integration patterns based on:
|
|
665
|
+
- [Next.js after() docs](https://nextjs.org/docs/app/api-reference/functions/after)
|
|
666
|
+
- [Vercel waitUntil](https://www.inngest.com/blog/vercel-cloudflare-wait-until)
|
|
667
|
+
- [Langfuse SDK patterns](https://langfuse.com/docs/observability/sdk/typescript/advanced-usage)
|