weflayr 0.3.1 → 0.4.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 +3 -215
- package/index.d.ts +7 -1
- package/package.json +1 -1
- package/src/instrument.js +20 -5
- package/src/providers/bedrock-runtime.d.ts +6 -0
- package/src/providers/bedrock-runtime.js +28 -0
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Weflayr Node.js SDK
|
|
2
2
|
|
|
3
|
-
Observability for Node.js
|
|
3
|
+
Observability for Node.js — instrument any LLM client or function with one line.
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
@@ -8,218 +8,6 @@ Observability for Node.js - instrument any LLM client or function with one line.
|
|
|
8
8
|
npm install weflayr
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
##
|
|
11
|
+
## Documentation
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
const { weflayr_setup, weflayr_instrument } = require('weflayr');
|
|
15
|
-
|
|
16
|
-
weflayr_setup({
|
|
17
|
-
intake_url: 'https://api.weflayr.com',
|
|
18
|
-
client_id: 'your-client-id-uuid',
|
|
19
|
-
client_secret: 'your-client-secret',
|
|
20
|
-
methods: [
|
|
21
|
-
{ call: 'chat.completions.create' },
|
|
22
|
-
],
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
const OpenAI = require('openai');
|
|
26
|
-
const client = weflayr_instrument(new OpenAI({ apiKey: process.env.OPENAI_API_KEY }));
|
|
27
|
-
|
|
28
|
-
const response = await client.chat.completions.create({
|
|
29
|
-
model: 'gpt-4o-mini',
|
|
30
|
-
messages: [{ role: 'user', content: 'Hello' }],
|
|
31
|
-
__weflayr_tags: { feature: 'chat', customer_id: '42' },
|
|
32
|
-
});
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
Credentials are typically read from environment variables via `dotenv`:
|
|
36
|
-
|
|
37
|
-
```js
|
|
38
|
-
weflayr_setup({
|
|
39
|
-
intake_url: process.env.WEFLAYR_INTAKE_URL,
|
|
40
|
-
client_id: process.env.WEFLAYR_CLIENT_ID,
|
|
41
|
-
client_secret: process.env.WEFLAYR_CLIENT_SECRET,
|
|
42
|
-
methods: [...],
|
|
43
|
-
});
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
```bash
|
|
47
|
-
# .env
|
|
48
|
-
WEFLAYR_INTAKE_URL=https://api.weflayr.com
|
|
49
|
-
WEFLAYR_CLIENT_ID=your-client-id-uuid
|
|
50
|
-
WEFLAYR_CLIENT_SECRET=your-client-secret
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
---
|
|
54
|
-
|
|
55
|
-
## Settings reference
|
|
56
|
-
|
|
57
|
-
| Field | Type | Required | Description |
|
|
58
|
-
|---|---|---|---|
|
|
59
|
-
| `intake_url` | `string` | ✓ | Base URL of the Weflayr intake API |
|
|
60
|
-
| `client_id` | `string` | ✓ | UUID identifying your Flare credential pair |
|
|
61
|
-
| `client_secret` | `string` | ✓ | Bearer token used to authenticate events |
|
|
62
|
-
| `event_mode` | `'default'` \| `'light'` | | `light` skips `before` events. Default: `'default'` |
|
|
63
|
-
| `enabled` | `boolean` | | Set to `false` to disable instrumentation entirely. Default: `true` |
|
|
64
|
-
| `ignore_fields` | `function` | | Middleware to strip sensitive fields from event payloads. Mutually exclusive with `allow_fields`. |
|
|
65
|
-
| `allow_fields` | `function` | | Middleware to keep only approved fields in event payloads. Mutually exclusive with `ignore_fields`. |
|
|
66
|
-
| `methods` | `MethodConfig[]` | | Methods to instrument on the proxied object |
|
|
67
|
-
|
|
68
|
-
---
|
|
69
|
-
|
|
70
|
-
## Filtering event payloads
|
|
71
|
-
|
|
72
|
-
`ignore_fields` and `allow_fields` are **middleware functions**, not field lists. Each receives a deep clone of the event payload (request args or response body) and returns the filtered version. The original args forwarded to the real provider call are never affected.
|
|
73
|
-
|
|
74
|
-
Only one may be set. Setting both logs a warning and blocks all events.
|
|
75
|
-
|
|
76
|
-
### ignore_fields strip specific fields
|
|
77
|
-
|
|
78
|
-
```js
|
|
79
|
-
weflayr_setup({
|
|
80
|
-
// ...
|
|
81
|
-
ignore_fields: (data) => {
|
|
82
|
-
(data.messages ?? []).forEach(m => delete m.content);
|
|
83
|
-
(data.choices ?? []).forEach(c => { if (c.message) delete c.message.content; });
|
|
84
|
-
return data;
|
|
85
|
-
},
|
|
86
|
-
methods: [{ call: 'chat.completions.create' }],
|
|
87
|
-
});
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
### allow_fields keep only specific fields
|
|
91
|
-
|
|
92
|
-
```js
|
|
93
|
-
weflayr_setup({
|
|
94
|
-
// ...
|
|
95
|
-
allow_fields: (data) => ({
|
|
96
|
-
model: data.model,
|
|
97
|
-
usage: data.usage,
|
|
98
|
-
}),
|
|
99
|
-
methods: [{ call: 'chat.completions.create' }],
|
|
100
|
-
});
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
---
|
|
104
|
-
|
|
105
|
-
## Metadata tags
|
|
106
|
-
|
|
107
|
-
Pass `__weflayr_tags` on any instrumented call to attach arbitrary key-value metadata to the event. Tags are stripped before the real provider call is made.
|
|
108
|
-
|
|
109
|
-
```js
|
|
110
|
-
await client.chat.completions.create({
|
|
111
|
-
model: 'gpt-4o-mini',
|
|
112
|
-
messages: [...],
|
|
113
|
-
__weflayr_tags: { feature: 'summarise', customer_id: '42' },
|
|
114
|
-
});
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
---
|
|
118
|
-
|
|
119
|
-
## Middleware
|
|
120
|
-
|
|
121
|
-
Middleware extracts structured fields that are merged into the event payload. `response` is `null` for `before` events.
|
|
122
|
-
|
|
123
|
-
```js
|
|
124
|
-
methods: [
|
|
125
|
-
{
|
|
126
|
-
call: 'audio.speech.create',
|
|
127
|
-
middleware: (args, response) => ({
|
|
128
|
-
char_count: args?.input?.length ?? 0,
|
|
129
|
-
result_count: response?.data?.length ?? 0,
|
|
130
|
-
}),
|
|
131
|
-
},
|
|
132
|
-
],
|
|
133
|
-
```
|
|
134
|
-
|
|
135
|
-
---
|
|
136
|
-
|
|
137
|
-
## Instrumenting a plain function
|
|
138
|
-
|
|
139
|
-
```js
|
|
140
|
-
async function fetchModels() {
|
|
141
|
-
const res = await fetch('https://api.example.com/models');
|
|
142
|
-
return res.json();
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
weflayr_setup({
|
|
146
|
-
// ...
|
|
147
|
-
methods: [
|
|
148
|
-
{
|
|
149
|
-
call: 'fetchModels',
|
|
150
|
-
middleware: (_args, response) => ({ count: response?.data?.length ?? 0 }),
|
|
151
|
-
},
|
|
152
|
-
],
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
const instrumented = weflayr_instrument(fetchModels);
|
|
156
|
-
const models = await instrumented({ __weflayr_tags: { app: 'my-app' } });
|
|
157
|
-
```
|
|
158
|
-
|
|
159
|
-
---
|
|
160
|
-
|
|
161
|
-
## Disabling instrumentation
|
|
162
|
-
|
|
163
|
-
```js
|
|
164
|
-
weflayr_setup({
|
|
165
|
-
// ...
|
|
166
|
-
enabled: false,
|
|
167
|
-
});
|
|
168
|
-
```
|
|
169
|
-
|
|
170
|
-
`weflayr_instrument` returns the original object untouched when `enabled` is `false`.
|
|
171
|
-
|
|
172
|
-
---
|
|
173
|
-
|
|
174
|
-
## TypeScript
|
|
175
|
-
|
|
176
|
-
The SDK ships with full type declarations. No additional `@types` package is needed.
|
|
177
|
-
|
|
178
|
-
```ts
|
|
179
|
-
import { weflayr_setup, weflayr_instrument, flayred } from 'weflayr';
|
|
180
|
-
import OpenAI from 'openai';
|
|
181
|
-
|
|
182
|
-
weflayr_setup({
|
|
183
|
-
intake_url: process.env.WEFLAYR_INTAKE_URL!,
|
|
184
|
-
client_id: process.env.WEFLAYR_CLIENT_ID!,
|
|
185
|
-
client_secret: process.env.WEFLAYR_CLIENT_SECRET!,
|
|
186
|
-
methods: [{ call: 'chat.completions.create' }],
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
const client = weflayr_instrument(new OpenAI({ apiKey: process.env.OPENAI_API_KEY }));
|
|
190
|
-
```
|
|
191
|
-
|
|
192
|
-
### Adding tags in TypeScript
|
|
193
|
-
|
|
194
|
-
Because `__weflayr_tags` is not part of the provider SDK's types, use the `flayred` helper to attach tags without casting:
|
|
195
|
-
|
|
196
|
-
```ts
|
|
197
|
-
const response = await client.chat.completions.create(flayred({
|
|
198
|
-
model: 'gpt-4o-mini',
|
|
199
|
-
messages: [{ role: 'user', content: 'Hello' }],
|
|
200
|
-
__weflayr_tags: { feature: 'chat', customer_id: '42' },
|
|
201
|
-
}));
|
|
202
|
-
```
|
|
203
|
-
|
|
204
|
-
`flayred<T>(options: T & { __weflayr_tags? }): T` TypeScript sees the return type as `T`, so it is fully compatible with the provider SDK's expected parameter type.
|
|
205
|
-
|
|
206
|
-
### Typing middleware
|
|
207
|
-
|
|
208
|
-
Cast inside the middleware body the SDK is provider-agnostic so it cannot infer the concrete response type:
|
|
209
|
-
|
|
210
|
-
```ts
|
|
211
|
-
import OpenAI from 'openai';
|
|
212
|
-
|
|
213
|
-
methods: [
|
|
214
|
-
{
|
|
215
|
-
call: 'chat.completions.create',
|
|
216
|
-
middleware: (_args, response) => {
|
|
217
|
-
const r = response as OpenAI.ChatCompletion | null;
|
|
218
|
-
return {
|
|
219
|
-
prompt_tokens: r?.usage?.prompt_tokens ?? 0,
|
|
220
|
-
completion_tokens: r?.usage?.completion_tokens ?? 0,
|
|
221
|
-
};
|
|
222
|
-
},
|
|
223
|
-
},
|
|
224
|
-
],
|
|
225
|
-
```
|
|
13
|
+
Full documentation, examples, and configuration reference: **[docs.weflayr.com](https://docs.weflayr.com)**
|
package/index.d.ts
CHANGED
|
@@ -36,13 +36,19 @@ export interface MethodConfig {
|
|
|
36
36
|
call: string;
|
|
37
37
|
middleware?: MiddlewareFn;
|
|
38
38
|
streamMiddleware?: StreamMiddlewareFn;
|
|
39
|
+
/**
|
|
40
|
+
* Property name on the response object that holds the async iterable stream.
|
|
41
|
+
* Use when the SDK returns `{ [streamPath]: AsyncIterable }` instead of a direct AsyncIterable.
|
|
42
|
+
* e.g. `'stream'` for AWS Bedrock's `ConverseStreamCommand`.
|
|
43
|
+
*/
|
|
44
|
+
streamPath?: string;
|
|
39
45
|
}
|
|
40
46
|
|
|
41
47
|
/** Full configuration object passed to `weflayr_setup`. */
|
|
42
48
|
export interface WeflayrSettings {
|
|
43
49
|
/** Base URL of the Weflayr intake API (e.g. `'https://api.weflayr.com'`). */
|
|
44
50
|
intake_url: string;
|
|
45
|
-
/** UUID identifying the
|
|
51
|
+
/** UUID identifying the flayr credential pair. */
|
|
46
52
|
client_id: string;
|
|
47
53
|
/** Bearer token used when calling the intake API. */
|
|
48
54
|
client_secret: string;
|
package/package.json
CHANGED
package/src/instrument.js
CHANGED
|
@@ -87,6 +87,12 @@ function _wrapFn(fn, methodName, methodConfig) {
|
|
|
87
87
|
return _wrapStream(result, methodName, tags, requestArgs, settings, startTime, methodConfig, eventId);
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
+
if (methodConfig.streamPath && result && _isAsyncIterable(result[methodConfig.streamPath])) {
|
|
91
|
+
_sendEvent('stream_start', { event_id: eventId, method: methodName, tags });
|
|
92
|
+
const wrappedStream = _wrapStream(result[methodConfig.streamPath], methodName, tags, requestArgs, settings, startTime, methodConfig, eventId);
|
|
93
|
+
return { ...result, [methodConfig.streamPath]: wrappedStream };
|
|
94
|
+
}
|
|
95
|
+
|
|
90
96
|
const middlewareData = methodConfig.middleware
|
|
91
97
|
? methodConfig.middleware(requestArgs, result) || {}
|
|
92
98
|
: {};
|
|
@@ -166,14 +172,23 @@ async function* _wrapStream(stream, methodName, tags, requestArgs, settings, sta
|
|
|
166
172
|
|
|
167
173
|
function _extractTags(args) {
|
|
168
174
|
let tags = {};
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
175
|
+
|
|
176
|
+
function strip(obj) {
|
|
177
|
+
if (!obj || typeof obj !== 'object' || Array.isArray(obj)) return;
|
|
178
|
+
if ('__weflayr_tags' in obj) {
|
|
179
|
+
tags = obj.__weflayr_tags || {};
|
|
180
|
+
delete obj.__weflayr_tags;
|
|
174
181
|
}
|
|
182
|
+
for (const key of Object.keys(obj)) {
|
|
183
|
+
strip(obj[key]);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const cleanArgs = args.map(arg => {
|
|
188
|
+
if (arg && typeof arg === 'object' && !Array.isArray(arg)) strip(arg);
|
|
175
189
|
return arg;
|
|
176
190
|
});
|
|
191
|
+
|
|
177
192
|
return { tags, cleanArgs };
|
|
178
193
|
}
|
|
179
194
|
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
function createStreamAccumulator() {
|
|
4
|
+
let input_tokens = null;
|
|
5
|
+
let output_tokens = null;
|
|
6
|
+
|
|
7
|
+
return {
|
|
8
|
+
onChunk(chunk) {
|
|
9
|
+
if (chunk.metadata?.usage) {
|
|
10
|
+
input_tokens = chunk.metadata.usage.inputTokens ?? null;
|
|
11
|
+
output_tokens = chunk.metadata.usage.outputTokens ?? null;
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
14
|
+
return false;
|
|
15
|
+
},
|
|
16
|
+
finalize() {
|
|
17
|
+
return { input_tokens, output_tokens };
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const converseStream = {
|
|
23
|
+
call: 'send',
|
|
24
|
+
streamPath: 'stream',
|
|
25
|
+
streamMiddleware: createStreamAccumulator,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
module.exports = { converseStream, createStreamAccumulator };
|