weflayr 0.16.1 → 0.20.3
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/.prettierrc.json +7 -0
- package/LICENSE-MIT +21 -0
- package/NOTICE +12 -0
- package/eslint.config.mjs +36 -0
- package/index.d.ts +125 -103
- package/package.json +19 -3
- package/src/auto-instrument.js +114 -0
- package/src/index.js +12 -34
- package/src/logger.js +8 -0
- package/src/otel/exporter.js +113 -0
- package/src/otel/openai-streaming-usage.js +60 -0
- package/src/otel/propagation.js +119 -0
- package/src/otel/span-filter.js +73 -0
- package/src/otel/span-processor.js +123 -0
- package/src/setup.js +168 -0
- package/tests/index.test.js +569 -980
- package/.env.example +0 -3
- package/src/instrument.js +0 -324
- package/src/providers/anthropic-ai-sdk.js +0 -63
- package/src/providers/bedrock-runtime.d.ts +0 -6
- package/src/providers/bedrock-runtime.js +0 -28
- package/src/vercel-ai-middleware.js +0 -124
package/.prettierrc.json
ADDED
package/LICENSE-MIT
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Langfuse GmbH
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/NOTICE
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
Weflayr Node.js SDK
|
|
2
|
+
Copyright 2026 Weflayr
|
|
3
|
+
|
|
4
|
+
This product includes software derived from langfuse-js
|
|
5
|
+
(https://github.com/langfuse/langfuse-js), Copyright 2024 Langfuse GmbH,
|
|
6
|
+
licensed under the MIT License.
|
|
7
|
+
|
|
8
|
+
Specifically, the OpenTelemetry span filter, span processor, and tag-propagation
|
|
9
|
+
helpers in src/otel/ are derived from langfuse-js packages/otel and packages/core
|
|
10
|
+
and have been adapted for the Weflayr intake API and tag schema.
|
|
11
|
+
|
|
12
|
+
See LICENSE-MIT for the full MIT license text.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export default [
|
|
2
|
+
{
|
|
3
|
+
languageOptions: {
|
|
4
|
+
ecmaVersion: 2023,
|
|
5
|
+
sourceType: "commonjs",
|
|
6
|
+
globals: {
|
|
7
|
+
// Node.js
|
|
8
|
+
require: "readonly",
|
|
9
|
+
module: "readonly",
|
|
10
|
+
exports: "writable",
|
|
11
|
+
__dirname: "readonly",
|
|
12
|
+
__filename: "readonly",
|
|
13
|
+
process: "readonly",
|
|
14
|
+
Buffer: "readonly",
|
|
15
|
+
console: "readonly",
|
|
16
|
+
setTimeout: "readonly",
|
|
17
|
+
clearTimeout: "readonly",
|
|
18
|
+
setInterval: "readonly",
|
|
19
|
+
clearInterval: "readonly",
|
|
20
|
+
// Web platform (available in modern Node)
|
|
21
|
+
fetch: "readonly",
|
|
22
|
+
URL: "readonly",
|
|
23
|
+
ReadableStream: "readonly",
|
|
24
|
+
TransformStream: "readonly",
|
|
25
|
+
TextEncoder: "readonly",
|
|
26
|
+
TextDecoder: "readonly",
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
rules: {
|
|
30
|
+
"no-unused-vars": ["warn", { argsIgnorePattern: "^_" }],
|
|
31
|
+
"no-undef": "error",
|
|
32
|
+
"prefer-const": "warn",
|
|
33
|
+
eqeqeq: ["error", "smart"],
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
];
|
package/index.d.ts
CHANGED
|
@@ -1,134 +1,156 @@
|
|
|
1
|
-
/** Arbitrary key-value
|
|
1
|
+
/** Arbitrary key-value tags applied to OTel spans. */
|
|
2
2
|
export type Tags = Record<string, string | number | boolean>;
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* Span attributes the mask callback can read or scrub. The OpenTelemetry GenAI
|
|
6
|
+
* semantic-convention keys get autocomplete; the index signature catches user
|
|
7
|
+
* tags and provider-specific attributes (e.g. Vercel AI's `ai.*`).
|
|
7
8
|
*/
|
|
8
|
-
export
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
response
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
9
|
+
export interface OtelSpanAttributes {
|
|
10
|
+
'gen_ai.system'?: string;
|
|
11
|
+
'gen_ai.operation.name'?: string;
|
|
12
|
+
'gen_ai.request.model'?: string;
|
|
13
|
+
'gen_ai.request.temperature'?: number;
|
|
14
|
+
'gen_ai.request.max_tokens'?: number;
|
|
15
|
+
'gen_ai.response.model'?: string;
|
|
16
|
+
'gen_ai.response.id'?: string;
|
|
17
|
+
'gen_ai.response.finish_reasons'?: string[];
|
|
18
|
+
'gen_ai.usage.input_tokens'?: number;
|
|
19
|
+
'gen_ai.usage.output_tokens'?: number;
|
|
20
|
+
/** Only populated when `captureMessageContent=true`. */
|
|
21
|
+
'gen_ai.prompt'?: string;
|
|
22
|
+
/** Only populated when `captureMessageContent=true`. */
|
|
23
|
+
'gen_ai.completion'?: string;
|
|
24
|
+
'gen_ai.tool.name'?: string;
|
|
25
|
+
'gen_ai.tool.call.id'?: string;
|
|
26
|
+
'gen_ai.tool.call.arguments'?: string;
|
|
27
|
+
'error.type'?: string;
|
|
28
|
+
[key: string]: unknown;
|
|
25
29
|
}
|
|
26
30
|
|
|
27
|
-
/**
|
|
28
|
-
export
|
|
31
|
+
/** LLM provider SDKs the `autoInstrument` registry knows how to wire up. */
|
|
32
|
+
export const AiSdk: {
|
|
33
|
+
readonly OPENAI: 'openai';
|
|
34
|
+
readonly ANTHROPIC: 'anthropic';
|
|
35
|
+
readonly BEDROCK: 'bedrock';
|
|
36
|
+
readonly AZURE: 'azure';
|
|
37
|
+
readonly COHERE: 'cohere';
|
|
38
|
+
readonly TOGETHER: 'together';
|
|
39
|
+
readonly GOOGLE_VERTEX: 'google-vertex';
|
|
40
|
+
readonly GOOGLE_GENAI: 'google-genai';
|
|
41
|
+
};
|
|
42
|
+
export type AiSdk = (typeof AiSdk)[keyof typeof AiSdk];
|
|
29
43
|
|
|
30
|
-
/**
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
44
|
+
/**
|
|
45
|
+
* Allowed values for `propagateMetadata({ providerName })`: every SDK the
|
|
46
|
+
* auto-instrumentation registry knows, plus providers reached via the OpenAI
|
|
47
|
+
* SDK on the wire but reported as themselves (e.g. Vercel AI Gateway).
|
|
48
|
+
*/
|
|
49
|
+
export const AIProviderName: {
|
|
50
|
+
readonly OPENAI: 'openai';
|
|
51
|
+
readonly ANTHROPIC: 'anthropic';
|
|
52
|
+
readonly BEDROCK: 'bedrock';
|
|
53
|
+
readonly AZURE: 'azure';
|
|
54
|
+
readonly COHERE: 'cohere';
|
|
55
|
+
readonly TOGETHER: 'together';
|
|
56
|
+
readonly GOOGLE_VERTEX: 'google-vertex';
|
|
57
|
+
readonly GOOGLE_GENAI: 'google-genai';
|
|
58
|
+
readonly VERCEL_AI_GATEWAY: 'vercel_ai_gateway';
|
|
59
|
+
};
|
|
60
|
+
export type AIProviderName = (typeof AIProviderName)[keyof typeof AIProviderName];
|
|
46
61
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
client_secret: string;
|
|
55
|
-
/** `'default'` emits before + after events; `'light'` emits only after. */
|
|
56
|
-
event_mode?: 'default' | 'light';
|
|
57
|
-
/** Set to `false` to disable instrumentation entirely. Defaults to `true`. */
|
|
58
|
-
enabled?: boolean;
|
|
62
|
+
export interface SetupOptions {
|
|
63
|
+
/** Weflayr API key. The server resolves your client_id from this token. */
|
|
64
|
+
apiKey: string;
|
|
65
|
+
/** Override the intake base URL. Defaults to WEFLAYR_BASE_URL or https://api.weflayr.com. */
|
|
66
|
+
baseUrl?: string;
|
|
67
|
+
/** Tags applied to every emitted span. */
|
|
68
|
+
defaultTags?: Tags;
|
|
59
69
|
/**
|
|
60
|
-
*
|
|
61
|
-
*
|
|
70
|
+
* `'batch'` (default) groups spans for throughput. `'immediate'` flushes every
|
|
71
|
+
* span synchronously — recommended for short-lived runtimes (Lambda, edge).
|
|
62
72
|
*/
|
|
63
|
-
|
|
73
|
+
exportMode?: 'batch' | 'immediate';
|
|
64
74
|
/**
|
|
65
|
-
*
|
|
66
|
-
*
|
|
75
|
+
* When `false`, sets OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT=false
|
|
76
|
+
* so instrumentations strip `gen_ai.prompt` / `gen_ai.completion` from spans
|
|
77
|
+
* before they're emitted. Defaults to `true`.
|
|
67
78
|
*/
|
|
68
|
-
|
|
79
|
+
captureMessageContent?: boolean;
|
|
69
80
|
/**
|
|
70
|
-
*
|
|
71
|
-
*
|
|
81
|
+
* Last-line-of-defense scrubber. Receives the span attribute bag just before
|
|
82
|
+
* export; return the (optionally mutated) bag.
|
|
72
83
|
*/
|
|
73
|
-
|
|
74
|
-
/** Methods to instrument on the proxied object. */
|
|
75
|
-
methods?: MethodConfig[];
|
|
84
|
+
mask?: (attrs: OtelSpanAttributes) => OtelSpanAttributes;
|
|
76
85
|
}
|
|
77
86
|
|
|
78
|
-
/**
|
|
79
|
-
export function
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Wraps an LLM client object or async function so matching method calls emit
|
|
83
|
-
* events to the intake API. Returns the original value unchanged when weflayr
|
|
84
|
-
* is disabled or the target has no matching method config.
|
|
85
|
-
*/
|
|
86
|
-
export function weflayr_instrument<T>(target: T): T;
|
|
87
|
+
/** Initializes the OpenTelemetry tracer + exporter. Call once at app start. */
|
|
88
|
+
export function setup(options: SetupOptions): void;
|
|
87
89
|
|
|
88
90
|
/**
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
91
|
+
* Loads and registers OpenTelemetry instrumentation packages for the given provider SDKs.
|
|
92
|
+
* Throws a clear "install package X" error if the matching package isn't installed.
|
|
93
|
+
*
|
|
94
|
+
* @example
|
|
95
|
+
* await weflayr.autoInstrument({ aiSdks: weflayr.AiSdk.OPENAI, apiKey: '...' });
|
|
96
|
+
* await weflayr.autoInstrument({ aiSdks: [weflayr.AiSdk.OPENAI, weflayr.AiSdk.ANTHROPIC] });
|
|
93
97
|
*/
|
|
94
|
-
export function
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
export function send_event(
|
|
98
|
-
eventType: string,
|
|
99
|
-
data: Record<string, unknown>,
|
|
100
|
-
): Promise<void> | undefined;
|
|
98
|
+
export function autoInstrument(
|
|
99
|
+
options: { aiSdks: AiSdk | AiSdk[] } & Partial<SetupOptions>
|
|
100
|
+
): Promise<void>;
|
|
101
101
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
102
|
+
export interface PropagateMetadataOptions {
|
|
103
|
+
/** Product-side feature triggering the LLM call (e.g. `"onboarding-summary"`). */
|
|
104
|
+
featureName: string;
|
|
105
|
+
/** Identifier of the end-user / tenant the call is made for. */
|
|
106
|
+
customerId: string;
|
|
107
|
+
/**
|
|
108
|
+
* Override for the provider seen on the wire. Set this only if it differs
|
|
109
|
+
* from the SDK auto-detected by OpenTelemetry — e.g. calls routed through
|
|
110
|
+
* `vercel_ai_gateway` that proxies to OpenAI/Anthropic but should be
|
|
111
|
+
* reported as the gateway.
|
|
112
|
+
*/
|
|
113
|
+
providerName?: AIProviderName;
|
|
114
|
+
/**
|
|
115
|
+
* Arbitrary scalar key/value pairs to attach. Values must be `string`,
|
|
116
|
+
* `number`, or `boolean` so they survive the ClickHouse `Map(String,
|
|
117
|
+
* String)` column they land in. Avoid `featureName` / `customerId` keys
|
|
118
|
+
* here — they have dedicated fields.
|
|
119
|
+
*/
|
|
120
|
+
extraTags?: Record<string, string | number | boolean>;
|
|
106
121
|
}
|
|
107
122
|
|
|
108
123
|
/**
|
|
109
|
-
*
|
|
110
|
-
*
|
|
124
|
+
* Attach Weflayr metadata to every LLM span created inside `fn`.
|
|
125
|
+
*
|
|
126
|
+
* Every invocation mints a fresh `uuid` that is stamped on every span inside
|
|
127
|
+
* the scope, so all calls made under one `propagateMetadata` block can be
|
|
128
|
+
* correlated downstream.
|
|
111
129
|
*
|
|
112
130
|
* @example
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
131
|
+
* await weflayr.propagateMetadata(
|
|
132
|
+
* { featureName: 'onboarding', customerId: '#3756', extraTags: { variant: 'control' } },
|
|
133
|
+
* async () => { await openai.chat.completions.create({ ... }); },
|
|
134
|
+
* );
|
|
117
135
|
*/
|
|
118
|
-
export function
|
|
136
|
+
export function propagateMetadata<T>(
|
|
137
|
+
options: PropagateMetadataOptions,
|
|
138
|
+
fn: () => Promise<T> | T
|
|
139
|
+
): Promise<T> | T;
|
|
119
140
|
|
|
120
141
|
/**
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
* `__weflayr_tags` is stripped by the instrumented proxy before the real provider call
|
|
124
|
-
* is made, so it never reaches the underlying SDK. At runtime this is the identity
|
|
125
|
-
* function — the generic signature is its only purpose.
|
|
142
|
+
* Decorator form. Apply to a class method to propagate metadata around every call.
|
|
143
|
+
* Each invocation of the wrapped function mints its own `uuid`.
|
|
126
144
|
*
|
|
127
145
|
* @example
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
* }));
|
|
146
|
+
* class ChatService {
|
|
147
|
+
* @weflayr.propagateMetadata({ featureName: 'chat', customerId: 'user_42' })
|
|
148
|
+
* async reply(userId: string) { ... }
|
|
149
|
+
* }
|
|
133
150
|
*/
|
|
134
|
-
export function
|
|
151
|
+
export function propagateMetadata(
|
|
152
|
+
options: PropagateMetadataOptions
|
|
153
|
+
): <T>(target: T, key?: string, descriptor?: PropertyDescriptor) => T;
|
|
154
|
+
|
|
155
|
+
/** Flush any pending spans. Await before a short-lived runtime exits. */
|
|
156
|
+
export function flush(): Promise<void>;
|
package/package.json
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "weflayr",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.20.3",
|
|
4
4
|
"description": "Weflayr Node.js SDK — instrument any LLM client via JS Proxy",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
7
7
|
"scripts": {
|
|
8
|
-
"test": "node --test tests/index.test.js"
|
|
8
|
+
"test": "node --test tests/index.test.js",
|
|
9
|
+
"lint": "eslint src tests",
|
|
10
|
+
"lint:fix": "eslint src tests --fix",
|
|
11
|
+
"format": "prettier --write src tests",
|
|
12
|
+
"format:check": "prettier --check src tests"
|
|
9
13
|
},
|
|
10
14
|
"keywords": [
|
|
11
15
|
"llm",
|
|
@@ -17,5 +21,17 @@
|
|
|
17
21
|
"engines": {
|
|
18
22
|
"node": ">=18.0.0"
|
|
19
23
|
},
|
|
20
|
-
"dependencies": {
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@opentelemetry/api": "^1.9.1",
|
|
26
|
+
"@opentelemetry/exporter-trace-otlp-http": "^0.219.0",
|
|
27
|
+
"@opentelemetry/instrumentation": "^0.219.0",
|
|
28
|
+
"@opentelemetry/resources": "^2.8.0",
|
|
29
|
+
"@opentelemetry/sdk-trace-base": "^2.8.0",
|
|
30
|
+
"@opentelemetry/sdk-trace-node": "^2.8.0",
|
|
31
|
+
"async-retry": "^1.3.3"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"eslint": "^9.18.0",
|
|
35
|
+
"prettier": "^3.4.2"
|
|
36
|
+
}
|
|
21
37
|
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { registerInstrumentations } = require('@opentelemetry/instrumentation');
|
|
4
|
+
|
|
5
|
+
const { debug } = require('./logger');
|
|
6
|
+
const { setup, _state } = require('./setup');
|
|
7
|
+
const { patchOpenAIStreamingUsage } = require('./otel/openai-streaming-usage');
|
|
8
|
+
|
|
9
|
+
const AiSdk = Object.freeze({
|
|
10
|
+
OPENAI: 'openai',
|
|
11
|
+
ANTHROPIC: 'anthropic',
|
|
12
|
+
BEDROCK: 'bedrock',
|
|
13
|
+
AZURE: 'azure',
|
|
14
|
+
COHERE: 'cohere',
|
|
15
|
+
TOGETHER: 'together',
|
|
16
|
+
GOOGLE_VERTEX: 'google-vertex',
|
|
17
|
+
GOOGLE_GENAI: 'google-genai',
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
// Allowed values for `propagateMetadata({ providerName })`: every SDK the
|
|
21
|
+
// auto-instrumentation registry knows, plus providers reached via the OpenAI
|
|
22
|
+
// SDK on the wire but reported as themselves (e.g. Vercel AI Gateway proxies
|
|
23
|
+
// to OpenAI/Anthropic but should be reported as the gateway).
|
|
24
|
+
const AIProviderName = Object.freeze({
|
|
25
|
+
...AiSdk,
|
|
26
|
+
VERCEL_AI_GATEWAY: 'vercel_ai_gateway',
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const REGISTRY = {
|
|
30
|
+
[AiSdk.OPENAI]: '@traceloop/instrumentation-openai',
|
|
31
|
+
[AiSdk.ANTHROPIC]: '@traceloop/instrumentation-anthropic',
|
|
32
|
+
[AiSdk.BEDROCK]: '@traceloop/instrumentation-bedrock',
|
|
33
|
+
[AiSdk.AZURE]: '@traceloop/instrumentation-azure',
|
|
34
|
+
[AiSdk.COHERE]: '@traceloop/instrumentation-cohere',
|
|
35
|
+
[AiSdk.TOGETHER]: '@traceloop/instrumentation-together',
|
|
36
|
+
[AiSdk.GOOGLE_VERTEX]: '@traceloop/instrumentation-vertexai',
|
|
37
|
+
[AiSdk.GOOGLE_GENAI]: '@traceloop/instrumentation-google-generativeai',
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const SETUP_OPTION_KEYS = [
|
|
41
|
+
'apiKey',
|
|
42
|
+
'baseUrl',
|
|
43
|
+
'defaultTags',
|
|
44
|
+
'exportMode',
|
|
45
|
+
'mask',
|
|
46
|
+
'captureMessageContent',
|
|
47
|
+
'warnOnExportError',
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
function _instantiateInstrumentation(instrumentationPkg) {
|
|
51
|
+
for (const value of Object.values(instrumentationPkg)) {
|
|
52
|
+
if (typeof value === 'function' && /Instrumentation$/.test(value.name)) {
|
|
53
|
+
return new value();
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (typeof instrumentationPkg.default === 'function') return new instrumentationPkg.default();
|
|
57
|
+
throw new Error('No Instrumentation class found in module exports');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function _instrumentAiSdk(aiSdk) {
|
|
61
|
+
if (!(aiSdk in REGISTRY)) {
|
|
62
|
+
throw new Error(
|
|
63
|
+
`weflayr.autoInstrument: unknown provider "${aiSdk}". Known: ${Object.keys(REGISTRY).join(', ')}`
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
const pkg = REGISTRY[aiSdk];
|
|
67
|
+
|
|
68
|
+
let instrumentationPkg;
|
|
69
|
+
try {
|
|
70
|
+
instrumentationPkg = require(pkg);
|
|
71
|
+
} catch (err) {
|
|
72
|
+
throw new Error(
|
|
73
|
+
`weflayr.autoInstrument("${aiSdk}"): failed to load "${pkg}". ` +
|
|
74
|
+
`Install it with: npm install ${pkg}\nUnderlying error: ${err.message}`
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const instrumentation = _instantiateInstrumentation(instrumentationPkg);
|
|
79
|
+
if (aiSdk === AiSdk.OPENAI) {
|
|
80
|
+
// The OpenAI instrumentor drops token usage on streamed calls; backfill it.
|
|
81
|
+
patchOpenAIStreamingUsage(instrumentation);
|
|
82
|
+
}
|
|
83
|
+
registerInstrumentations({ instrumentations: [instrumentation] });
|
|
84
|
+
debug('autoInstrument: registered aiSdk=%s package=%s', aiSdk, pkg);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Register an OTel Instrumentor for one or more LLM provider SDKs.
|
|
89
|
+
*
|
|
90
|
+
* Convenience: pass `apiKey` (+ other `setup()` options) here to skip a
|
|
91
|
+
* separate `weflayr.setup()` call. If `apiKey` is omitted, `setup()` must
|
|
92
|
+
* have been called beforehand.
|
|
93
|
+
*/
|
|
94
|
+
async function autoInstrument({ aiSdks, ...options } = {}) {
|
|
95
|
+
if (aiSdks === undefined) {
|
|
96
|
+
throw new Error('weflayr.autoInstrument: `aiSdks` is required.');
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (!_state.processor) {
|
|
100
|
+
// setup() picks up WEFLAYR_API_KEY from the env if not passed here.
|
|
101
|
+
const setupOptions = {};
|
|
102
|
+
for (const key of SETUP_OPTION_KEYS) {
|
|
103
|
+
if (options[key] !== undefined) setupOptions[key] = options[key];
|
|
104
|
+
}
|
|
105
|
+
setup(setupOptions);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const list = Array.isArray(aiSdks) ? aiSdks : [aiSdks];
|
|
109
|
+
for (const aiSdk of list) {
|
|
110
|
+
await _instrumentAiSdk(aiSdk);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
module.exports = { autoInstrument, AiSdk, AIProviderName, REGISTRY };
|
package/src/index.js
CHANGED
|
@@ -1,36 +1,14 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const {
|
|
4
|
-
const {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
if (!intake_url || !client_id || !client_secret) {
|
|
17
|
-
throw new Error(
|
|
18
|
-
'Weflayr: intake_url, client_id and client_secret must be set in settings'
|
|
19
|
-
);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
configure(intake_url, client_id, client_secret, settings);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Attaches weflayr metadata tags to any SDK call options object.
|
|
27
|
-
*
|
|
28
|
-
* @template T
|
|
29
|
-
* @param {T & { __weflayr_tags?: Record<string, string | number | boolean> }} options
|
|
30
|
-
* @returns {T}
|
|
31
|
-
*/
|
|
32
|
-
function flayred(options) {
|
|
33
|
-
return options;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
module.exports = { weflayr_setup, weflayr_instrument, weflayr_propagate, send_event, flayred, weflayr_vercel_ai_middleware };
|
|
3
|
+
const { setup, flush } = require('./setup');
|
|
4
|
+
const { autoInstrument, AiSdk, AIProviderName } = require('./auto-instrument');
|
|
5
|
+
const { propagateMetadata } = require('./otel/propagation');
|
|
6
|
+
|
|
7
|
+
module.exports = {
|
|
8
|
+
setup,
|
|
9
|
+
flush,
|
|
10
|
+
autoInstrument,
|
|
11
|
+
AiSdk,
|
|
12
|
+
AIProviderName,
|
|
13
|
+
propagateMetadata,
|
|
14
|
+
};
|