weflayr 0.27.0 → 0.28.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/dist/package.json +3 -3
- package/dist/src/auto-instrument.d.ts +4 -3
- package/dist/src/auto-instrument.js +18 -17
- package/dist/src/get-data-uri-mime-type.d.ts +17 -0
- package/dist/src/get-data-uri-mime-type.js +25 -0
- package/dist/src/index.d.ts +3 -2
- package/dist/src/index.js +5 -1
- package/dist/src/instrumentation/anthropic/get_streaming_accumulation_telemetry.d.ts +3 -1
- package/dist/src/instrumentation/anthropic/get_streaming_accumulation_telemetry.js +6 -3
- package/dist/src/instrumentation/anthropic/get_vertex_billing_telemetry.d.ts +32 -0
- package/dist/src/instrumentation/anthropic/get_vertex_billing_telemetry.js +100 -0
- package/dist/src/instrumentation/anthropic/get_web_search_telemetry.d.ts +43 -0
- package/dist/src/instrumentation/anthropic/get_web_search_telemetry.js +147 -0
- package/dist/src/instrumentation/anthropic/set_telemetry_attributes.d.ts +5 -2
- package/dist/src/instrumentation/anthropic/set_telemetry_attributes.js +17 -4
- package/dist/src/instrumentation/bedrock/get_cache_config_telemetry.d.ts +13 -1
- package/dist/src/instrumentation/bedrock/get_cache_config_telemetry.js +56 -1
- package/dist/src/instrumentation/bedrock/get_cache_telemetry.js +8 -5
- package/dist/src/instrumentation/bedrock/get_model_id_telemetry.d.ts +1 -1
- package/dist/src/instrumentation/bedrock/get_model_id_telemetry.js +1 -1
- package/dist/src/instrumentation/bedrock/get_openai_chat_format_telemetry.d.ts +45 -0
- package/dist/src/instrumentation/bedrock/get_openai_chat_format_telemetry.js +251 -0
- package/dist/src/instrumentation/bedrock/get_streaming_output_message_telemetry.d.ts +6 -5
- package/dist/src/instrumentation/bedrock/get_streaming_output_message_telemetry.js +15 -12
- package/dist/src/instrumentation/bedrock/get_tool_definitions_telemetry.d.ts +2 -2
- package/dist/src/instrumentation/bedrock/get_tool_definitions_telemetry.js +12 -4
- package/dist/src/instrumentation/bedrock/set_telemetry_attributes.d.ts +4 -2
- package/dist/src/instrumentation/bedrock/set_telemetry_attributes.js +10 -3
- package/dist/src/instrumentation/cohere/get_embeddings_telemetry.js +4 -6
- package/dist/src/instrumentation/cohere/get_input_message_content_telemetry.d.ts +23 -0
- package/dist/src/instrumentation/cohere/get_input_message_content_telemetry.js +105 -0
- package/dist/src/instrumentation/cohere/get_v2_chat_telemetry.d.ts +18 -0
- package/dist/src/instrumentation/cohere/get_v2_chat_telemetry.js +186 -0
- package/dist/src/instrumentation/cohere/set_telemetry_attributes.d.ts +3 -1
- package/dist/src/instrumentation/cohere/set_telemetry_attributes.js +5 -1
- package/dist/src/instrumentation/google/get_embeddings_telemetry.d.ts +8 -4
- package/dist/src/instrumentation/google/get_embeddings_telemetry.js +68 -35
- package/dist/src/instrumentation/google/get_provider_name.d.ts +2 -1
- package/dist/src/instrumentation/google/get_provider_name.js +2 -1
- package/dist/src/instrumentation/google/get_web_search_telemetry.d.ts +38 -0
- package/dist/src/instrumentation/google/get_web_search_telemetry.js +125 -0
- package/dist/src/instrumentation/google/set_telemetry_attributes.d.ts +2 -2
- package/dist/src/instrumentation/google/set_telemetry_attributes.js +6 -2
- package/dist/src/instrumentation/mistral/full_instrumentation.d.ts +60 -0
- package/dist/src/instrumentation/mistral/full_instrumentation.js +364 -0
- package/dist/src/instrumentation/mistral/get_input_message_content_telemetry.d.ts +35 -0
- package/dist/src/instrumentation/mistral/get_input_message_content_telemetry.js +113 -0
- package/dist/src/instrumentation/mistral/get_structured_output_schema_telemetry.d.ts +13 -0
- package/dist/src/instrumentation/mistral/get_structured_output_schema_telemetry.js +24 -0
- package/dist/src/instrumentation/mistral/get_vertex_telemetry.d.ts +54 -0
- package/dist/src/instrumentation/mistral/get_vertex_telemetry.js +153 -0
- package/dist/src/instrumentation/openai/get_bedrock_mantle_telemetry.d.ts +10 -0
- package/dist/src/instrumentation/openai/get_bedrock_mantle_telemetry.js +33 -0
- package/dist/src/instrumentation/openai/get_cache_and_reasoning_telemetry.d.ts +13 -6
- package/dist/src/instrumentation/openai/get_cache_and_reasoning_telemetry.js +17 -7
- package/dist/src/instrumentation/openai/get_cache_config_telemetry.d.ts +21 -4
- package/dist/src/instrumentation/openai/get_cache_config_telemetry.js +50 -11
- package/dist/src/instrumentation/openai/get_embeddings_telemetry.js +14 -6
- package/dist/src/instrumentation/openai/get_file_part_mime_type_telemetry.d.ts +26 -0
- package/dist/src/instrumentation/openai/get_file_part_mime_type_telemetry.js +100 -0
- package/dist/src/instrumentation/openai/get_openai_compatible_provider_telemetry.d.ts +48 -0
- package/dist/src/instrumentation/openai/get_openai_compatible_provider_telemetry.js +123 -0
- package/dist/src/instrumentation/openai/get_reasoning_content_telemetry.d.ts +28 -0
- package/dist/src/instrumentation/openai/get_reasoning_content_telemetry.js +130 -0
- package/dist/src/instrumentation/openai/get_responses_telemetry.d.ts +89 -0
- package/dist/src/instrumentation/openai/get_responses_telemetry.js +587 -0
- package/dist/src/instrumentation/openai/get_service_tier_telemetry.d.ts +20 -0
- package/dist/src/instrumentation/openai/get_service_tier_telemetry.js +84 -0
- package/dist/src/instrumentation/openai/get_structured_output_schema_telemetry.d.ts +7 -5
- package/dist/src/instrumentation/openai/get_structured_output_schema_telemetry.js +15 -8
- package/dist/src/instrumentation/openai/get_vertex_openai_compatible_telemetry.d.ts +23 -0
- package/dist/src/instrumentation/openai/get_vertex_openai_compatible_telemetry.js +69 -0
- package/dist/src/instrumentation/openai/get_web_search_telemetry.d.ts +74 -0
- package/dist/src/instrumentation/openai/get_web_search_telemetry.js +259 -0
- package/dist/src/instrumentation/openai/set_telemetry_attributes.d.ts +13 -1
- package/dist/src/instrumentation/openai/set_telemetry_attributes.js +33 -2
- package/dist/src/instrumentation/shared/get_structured_output_schema_telemetry.d.ts +1 -1
- package/dist/src/instrumentation/shared/get_structured_output_schema_telemetry.js +1 -1
- package/dist/src/instrumentation/shared/is_vertex_base_url.d.ts +17 -0
- package/dist/src/instrumentation/shared/is_vertex_base_url.js +40 -0
- package/dist/src/otel/propagation.d.ts +37 -10
- package/dist/src/otel/propagation.js +62 -22
- package/dist/src/otel/span-filter.d.ts +2 -0
- package/dist/src/otel/span-filter.js +10 -1
- package/dist/src/otel/span-processor.d.ts +1 -2
- package/dist/src/otel/span-processor.js +17 -20
- package/dist/src/otel/strip-media-payloads.d.ts +26 -0
- package/dist/src/otel/strip-media-payloads.js +135 -0
- package/dist/src/propagate-metadata-to-gateway.d.ts +37 -0
- package/dist/src/propagate-metadata-to-gateway.js +213 -0
- package/package.json +3 -3
- package/dist/src/instrumentation/vercel/full_instrumentation.d.ts +0 -3
- package/dist/src/instrumentation/vercel/full_instrumentation.js +0 -54
- package/dist/src/instrumentation/vercel/get_cache_creation_telemetry.d.ts +0 -22
- package/dist/src/instrumentation/vercel/get_cache_creation_telemetry.js +0 -66
- package/dist/src/instrumentation/vercel/get_structured_output_schema_telemetry.d.ts +0 -24
- package/dist/src/instrumentation/vercel/get_structured_output_schema_telemetry.js +0 -84
- package/dist/src/otel/vercel-ai-sdk-span-filter.d.ts +0 -12
- package/dist/src/otel/vercel-ai-sdk-span-filter.js +0 -88
|
@@ -8,7 +8,19 @@
|
|
|
8
8
|
* non-streamed cached-prompt tokens (these enrich the traceloop instrumentor
|
|
9
9
|
* instance, hence the `instrumentation` argument), and
|
|
10
10
|
* - the request's structured-output schema, and
|
|
11
|
-
* -
|
|
11
|
+
* - the Responses endpoint, which it wraps but records far less of than the
|
|
12
|
+
* Python instrumentor does (and not at all when streamed), and
|
|
13
|
+
* - Bedrock Mantle's provider attribution and reasoning output, and
|
|
14
|
+
* - the provider-run web search (`web_search_options` and the answer's
|
|
15
|
+
* `url_citation` annotations), and
|
|
16
|
+
* - the mime type of an inline file (PDF, ...), which it reads from a
|
|
17
|
+
* field OpenAI's API does not have and so always records as
|
|
18
|
+
* application/octet-stream, and
|
|
19
|
+
* - the provider attribution of the vendors served over an
|
|
20
|
+
* OpenAI-compatible API (Alibaba Model Studio, xAI), and
|
|
21
|
+
* - the billing of the calls to Vertex AI's OpenAI-compatible endpoint, and
|
|
22
|
+
* - a `reasoning_content` output part for any provider that returns one, and
|
|
23
|
+
* - the service tier the call was served in (flex, priority, ...).
|
|
12
24
|
*/
|
|
13
25
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
26
|
exports.OpenAITelemetryInstrumentor = void 0;
|
|
@@ -16,9 +28,16 @@ const get_structured_output_schema_telemetry_1 = require("../shared/get_structur
|
|
|
16
28
|
const get_audio_telemetry_1 = require("./get_audio_telemetry");
|
|
17
29
|
const get_bedrock_mantle_telemetry_1 = require("./get_bedrock_mantle_telemetry");
|
|
18
30
|
const get_cache_config_telemetry_1 = require("./get_cache_config_telemetry");
|
|
31
|
+
const get_file_part_mime_type_telemetry_1 = require("./get_file_part_mime_type_telemetry");
|
|
32
|
+
const get_openai_compatible_provider_telemetry_1 = require("./get_openai_compatible_provider_telemetry");
|
|
33
|
+
const get_responses_telemetry_1 = require("./get_responses_telemetry");
|
|
19
34
|
const get_embeddings_telemetry_1 = require("./get_embeddings_telemetry");
|
|
35
|
+
const get_reasoning_content_telemetry_1 = require("./get_reasoning_content_telemetry");
|
|
36
|
+
const get_service_tier_telemetry_1 = require("./get_service_tier_telemetry");
|
|
20
37
|
const get_streaming_usage_1 = require("./get_streaming_usage");
|
|
21
38
|
const get_cache_and_reasoning_telemetry_1 = require("./get_cache_and_reasoning_telemetry");
|
|
39
|
+
const get_vertex_openai_compatible_telemetry_1 = require("./get_vertex_openai_compatible_telemetry");
|
|
40
|
+
const get_web_search_telemetry_1 = require("./get_web_search_telemetry");
|
|
22
41
|
const get_structured_output_schema_telemetry_2 = require("./get_structured_output_schema_telemetry");
|
|
23
42
|
class OpenAITelemetryInstrumentor {
|
|
24
43
|
_subInstrumentors;
|
|
@@ -29,7 +48,15 @@ class OpenAITelemetryInstrumentor {
|
|
|
29
48
|
new get_streaming_usage_1.OpenAIStreamingUsageInstrumentor(),
|
|
30
49
|
new get_cache_and_reasoning_telemetry_1.OpenAIPromptCachingInstrumentor(),
|
|
31
50
|
new get_cache_config_telemetry_1.OpenAICacheConfigInstrumentor(),
|
|
51
|
+
new get_responses_telemetry_1.OpenAIResponsesInstrumentor(),
|
|
52
|
+
new get_file_part_mime_type_telemetry_1.OpenAIFilePartMimeTypeInstrumentor(),
|
|
32
53
|
new get_bedrock_mantle_telemetry_1.OpenAIBedrockMantleInstrumentor(),
|
|
54
|
+
new get_web_search_telemetry_1.OpenAIWebSearchInstrumentor(),
|
|
55
|
+
new get_openai_compatible_provider_telemetry_1.OpenAICompatibleProviderInstrumentor(get_openai_compatible_provider_telemetry_1.ALIBABA_PROVIDER),
|
|
56
|
+
new get_openai_compatible_provider_telemetry_1.OpenAICompatibleProviderInstrumentor(get_openai_compatible_provider_telemetry_1.SPACEXAI_PROVIDER),
|
|
57
|
+
new get_vertex_openai_compatible_telemetry_1.VertexOpenAICompatibleInstrumentor(),
|
|
58
|
+
new get_reasoning_content_telemetry_1.OpenAIReasoningContentInstrumentor(),
|
|
59
|
+
new get_service_tier_telemetry_1.OpenAIServiceTierInstrumentor(),
|
|
33
60
|
new get_structured_output_schema_telemetry_1.TraceloopStructuredOutputSchemaInstrumentor(get_structured_output_schema_telemetry_2.readStructuredOutputSchema),
|
|
34
61
|
];
|
|
35
62
|
}
|
|
@@ -39,8 +66,12 @@ class OpenAITelemetryInstrumentor {
|
|
|
39
66
|
for (const sub of this._subInstrumentors)
|
|
40
67
|
sub.instrument(instrumentation);
|
|
41
68
|
}
|
|
69
|
+
// Reversed: several complements patch the same instrumentor methods
|
|
70
|
+
// (`startSpan`, `_endSpan`), so the patches have to be unwound
|
|
71
|
+
// last-patched-first or an earlier wrapper is written back over a later one
|
|
72
|
+
// and stays installed.
|
|
42
73
|
uninstrument() {
|
|
43
|
-
for (const sub of this._subInstrumentors)
|
|
74
|
+
for (const sub of [...this._subInstrumentors].reverse())
|
|
44
75
|
sub.uninstrument();
|
|
45
76
|
}
|
|
46
77
|
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Each provider asks for structured output under its own request field (OpenAI
|
|
5
5
|
* `response_format`, Anthropic `output_config.format`, Cohere `response_format`,
|
|
6
6
|
* Gemini `responseJsonSchema`), but they all serialize the schema into the
|
|
7
|
-
* request the same way - and OpenAI
|
|
7
|
+
* request the same way - and OpenAI and Anthropic bill it as input - so
|
|
8
8
|
* the complements stamp one provider-agnostic attribute and the worker stores it
|
|
9
9
|
* in `input_structured_output_schema`.
|
|
10
10
|
*
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Each provider asks for structured output under its own request field (OpenAI
|
|
6
6
|
* `response_format`, Anthropic `output_config.format`, Cohere `response_format`,
|
|
7
7
|
* Gemini `responseJsonSchema`), but they all serialize the schema into the
|
|
8
|
-
* request the same way - and OpenAI
|
|
8
|
+
* request the same way - and OpenAI and Anthropic bill it as input - so
|
|
9
9
|
* the complements stamp one provider-agnostic attribute and the worker stores it
|
|
10
10
|
* in `input_structured_output_schema`.
|
|
11
11
|
*
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether a client calls Google Cloud's Vertex AI.
|
|
3
|
+
*
|
|
4
|
+
* Vertex serves partner models through the providers' own clients pointed at a
|
|
5
|
+
* Vertex AI host: Claude through Anthropic's `AnthropicVertex`, and Grok,
|
|
6
|
+
* DeepSeek, Llama and the other partner models through the openai client
|
|
7
|
+
* pointed at Vertex's OpenAI-compatible endpoint. Those calls are billed by
|
|
8
|
+
* Google at Vertex's prices, so each complement that sees such a client stamps
|
|
9
|
+
* `weflayr.billing_provider` with `vertex`.
|
|
10
|
+
*/
|
|
11
|
+
export declare const VERTEX_BILLING_PROVIDER = "vertex";
|
|
12
|
+
export declare const BILLING_PROVIDER_ATTR = "weflayr.billing_provider";
|
|
13
|
+
/** Whether a client base URL is a Vertex AI host, e.g.
|
|
14
|
+
* 'https://us-east5-aiplatform.googleapis.com/v1' -> true. Never a bare
|
|
15
|
+
* substring match, so 'https://aiplatform.googleapis.com.evil.example' is not
|
|
16
|
+
* one. */
|
|
17
|
+
export declare function isVertexBaseURL(baseURL: unknown): boolean;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Whether a client calls Google Cloud's Vertex AI.
|
|
4
|
+
*
|
|
5
|
+
* Vertex serves partner models through the providers' own clients pointed at a
|
|
6
|
+
* Vertex AI host: Claude through Anthropic's `AnthropicVertex`, and Grok,
|
|
7
|
+
* DeepSeek, Llama and the other partner models through the openai client
|
|
8
|
+
* pointed at Vertex's OpenAI-compatible endpoint. Those calls are billed by
|
|
9
|
+
* Google at Vertex's prices, so each complement that sees such a client stamps
|
|
10
|
+
* `weflayr.billing_provider` with `vertex`.
|
|
11
|
+
*/
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.BILLING_PROVIDER_ATTR = exports.VERTEX_BILLING_PROVIDER = void 0;
|
|
14
|
+
exports.isVertexBaseURL = isVertexBaseURL;
|
|
15
|
+
exports.VERTEX_BILLING_PROVIDER = 'vertex';
|
|
16
|
+
exports.BILLING_PROVIDER_ATTR = 'weflayr.billing_provider';
|
|
17
|
+
// The global host and the US and EU multi-region hosts. A single Google region
|
|
18
|
+
// is served on its own host, "<region>-aiplatform.googleapis.com".
|
|
19
|
+
const VERTEX_HOSTNAMES = new Set([
|
|
20
|
+
'aiplatform.googleapis.com',
|
|
21
|
+
'aiplatform.us.rep.googleapis.com',
|
|
22
|
+
'aiplatform.eu.rep.googleapis.com',
|
|
23
|
+
]);
|
|
24
|
+
const VERTEX_REGIONAL_HOSTNAME_SUFFIX = '-aiplatform.googleapis.com';
|
|
25
|
+
/** Whether a client base URL is a Vertex AI host, e.g.
|
|
26
|
+
* 'https://us-east5-aiplatform.googleapis.com/v1' -> true. Never a bare
|
|
27
|
+
* substring match, so 'https://aiplatform.googleapis.com.evil.example' is not
|
|
28
|
+
* one. */
|
|
29
|
+
function isVertexBaseURL(baseURL) {
|
|
30
|
+
if (typeof baseURL !== 'string' || !baseURL)
|
|
31
|
+
return false;
|
|
32
|
+
let hostname;
|
|
33
|
+
try {
|
|
34
|
+
hostname = new URL(baseURL).hostname.toLowerCase();
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
return VERTEX_HOSTNAMES.has(hostname) || hostname.endsWith(VERTEX_REGIONAL_HOSTNAME_SUFFIX);
|
|
40
|
+
}
|
|
@@ -3,25 +3,21 @@ import type { AIProviderName } from '../auto-instrument';
|
|
|
3
3
|
declare const WEFLAYR_METADATA_CONTEXT_KEY: symbol;
|
|
4
4
|
declare const FEATURE_NAME_ATTR = "weflayr.feature_name";
|
|
5
5
|
declare const CUSTOMER_NAME_ATTR = "weflayr.customer_name";
|
|
6
|
-
declare const
|
|
6
|
+
declare const CALL_NAME_ATTR = "weflayr.call_name";
|
|
7
|
+
declare const BILLING_PROVIDER_ATTR = "weflayr.billing_provider";
|
|
7
8
|
declare const UUID_ATTR = "weflayr.uuid";
|
|
8
9
|
declare const EXTRA_TAG_PREFIX = "weflayr.tag.";
|
|
9
10
|
export interface PropagateMetadataOptions {
|
|
10
11
|
/** Product-side feature triggering the LLM call (e.g. `"onboarding-summary"`). */
|
|
11
12
|
featureName: string;
|
|
12
13
|
/** Name of the end-user / tenant customer the call is made for. */
|
|
13
|
-
customerName
|
|
14
|
-
/** @deprecated Use `customerName` instead. */
|
|
15
|
-
customerId?: string;
|
|
14
|
+
customerName: string;
|
|
16
15
|
/**
|
|
17
16
|
* Override for the provider seen on the wire. Set this only if it differs
|
|
18
|
-
* from the SDK auto-detected by OpenTelemetry
|
|
19
|
-
*
|
|
20
|
-
* reported as the gateway.
|
|
17
|
+
* from the SDK auto-detected by OpenTelemetry, e.g. Mistral calls made
|
|
18
|
+
* through the OpenAI SDK, which should be reported as `mistral`.
|
|
21
19
|
*/
|
|
22
20
|
providerNameOverride?: AIProviderName;
|
|
23
|
-
/** @deprecated Use `providerNameOverride` instead. */
|
|
24
|
-
providerName?: AIProviderName;
|
|
25
21
|
/**
|
|
26
22
|
* Arbitrary scalar key/value pairs to attach. Values must be `string`,
|
|
27
23
|
* `number`, or `boolean` so they survive the ClickHouse `Map(String,
|
|
@@ -29,8 +25,24 @@ export interface PropagateMetadataOptions {
|
|
|
29
25
|
* here — they have dedicated fields.
|
|
30
26
|
*/
|
|
31
27
|
extraTags?: Record<string, string | number | boolean>;
|
|
28
|
+
/**
|
|
29
|
+
* The uuid to stamp on every span inside the scope, e.g. one
|
|
30
|
+
* `createFeatureRunUuid` made where the run started, so the calls of one run
|
|
31
|
+
* made in several processes, or in several scopes, share it: 32 lowercase
|
|
32
|
+
* hexadecimal characters. When omitted, each invocation creates its own.
|
|
33
|
+
*/
|
|
34
|
+
featureRunUuid?: string;
|
|
32
35
|
}
|
|
33
36
|
export declare function getMergedMetadataFromContext(parentContext?: Context): Record<string, unknown>;
|
|
37
|
+
/**
|
|
38
|
+
* A new feature run uuid, the kind `propagateMetadata` creates for each scope,
|
|
39
|
+
* e.g. `"3f2b9c1e0d4a4b6f8e7d5c4b3a291807"`.
|
|
40
|
+
*
|
|
41
|
+
* Create one where a run starts and pass it as `featureRunUuid` to every
|
|
42
|
+
* `propagateMetadata` scope of the run, including the ones opened in other
|
|
43
|
+
* processes, so all its calls are recorded under one feature run.
|
|
44
|
+
*/
|
|
45
|
+
export declare function createFeatureRunUuid(): string;
|
|
34
46
|
/**
|
|
35
47
|
* Attach Weflayr metadata to every LLM span created inside this scope.
|
|
36
48
|
*
|
|
@@ -43,4 +55,19 @@ export declare function getMergedMetadataFromContext(parentContext?: Context): R
|
|
|
43
55
|
*/
|
|
44
56
|
export declare function propagateMetadata<T>(options: PropagateMetadataOptions, fn: () => Promise<T> | T): Promise<T> | T;
|
|
45
57
|
export declare function propagateMetadata(options: PropagateMetadataOptions): (target: unknown, key?: string, descriptor?: PropertyDescriptor) => unknown;
|
|
46
|
-
|
|
58
|
+
/**
|
|
59
|
+
* Name the LLM call made inside this scope, e.g. `"summarize_profile"`.
|
|
60
|
+
*
|
|
61
|
+
* Callable form: `setCallName(callName, fn)` runs `fn` immediately. Decorator
|
|
62
|
+
* form: `setCallName(callName)` returns a wrapper usable as a function-decorator
|
|
63
|
+
* (`@setCallName(...)` on class methods) or as a plain `wrap(fn)` -> wrappedFn
|
|
64
|
+
* helper.
|
|
65
|
+
*
|
|
66
|
+
* Where `featureName` names the product feature a call belongs to, `callName`
|
|
67
|
+
* names one call within it. The fields of an enclosing `propagateMetadata`
|
|
68
|
+
* scope are kept, and no new `uuid` is minted, so calls under different names
|
|
69
|
+
* still share their feature's uuid.
|
|
70
|
+
*/
|
|
71
|
+
export declare function setCallName<T>(callName: string, fn: () => Promise<T> | T): Promise<T> | T;
|
|
72
|
+
export declare function setCallName(callName: string): (target: unknown, key?: string, descriptor?: PropertyDescriptor) => unknown;
|
|
73
|
+
export { WEFLAYR_METADATA_CONTEXT_KEY, FEATURE_NAME_ATTR, CUSTOMER_NAME_ATTR, CALL_NAME_ATTR, BILLING_PROVIDER_ATTR, UUID_ATTR, EXTRA_TAG_PREFIX, };
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
// - Replaced Langfuse's fixed schema with Weflayr's structured metadata
|
|
10
10
|
// (featureName, customerName, providerNameOverride, extraTags) plus an
|
|
11
11
|
// auto-generated per-call uuid
|
|
12
|
+
// - Added `setCallName`, a nested scope naming one LLM call
|
|
12
13
|
// - Removed asBaggage cross-process propagation (not needed yet)
|
|
13
14
|
// - Removed experiment framework keys
|
|
14
15
|
// - Structured fields are written under stable `weflayr.*` span attribute
|
|
@@ -16,9 +17,11 @@
|
|
|
16
17
|
//
|
|
17
18
|
// See LICENSE-MIT in the package root for the full license text.
|
|
18
19
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
-
exports.EXTRA_TAG_PREFIX = exports.UUID_ATTR = exports.
|
|
20
|
+
exports.EXTRA_TAG_PREFIX = exports.UUID_ATTR = exports.BILLING_PROVIDER_ATTR = exports.CALL_NAME_ATTR = exports.CUSTOMER_NAME_ATTR = exports.FEATURE_NAME_ATTR = exports.WEFLAYR_METADATA_CONTEXT_KEY = void 0;
|
|
20
21
|
exports.getMergedMetadataFromContext = getMergedMetadataFromContext;
|
|
22
|
+
exports.createFeatureRunUuid = createFeatureRunUuid;
|
|
21
23
|
exports.propagateMetadata = propagateMetadata;
|
|
24
|
+
exports.setCallName = setCallName;
|
|
22
25
|
const node_crypto_1 = require("node:crypto");
|
|
23
26
|
const api_1 = require("@opentelemetry/api");
|
|
24
27
|
const WEFLAYR_METADATA_CONTEXT_KEY = (0, api_1.createContextKey)('weflayr_metadata');
|
|
@@ -27,8 +30,10 @@ const FEATURE_NAME_ATTR = 'weflayr.feature_name';
|
|
|
27
30
|
exports.FEATURE_NAME_ATTR = FEATURE_NAME_ATTR;
|
|
28
31
|
const CUSTOMER_NAME_ATTR = 'weflayr.customer_name';
|
|
29
32
|
exports.CUSTOMER_NAME_ATTR = CUSTOMER_NAME_ATTR;
|
|
30
|
-
const
|
|
31
|
-
exports.
|
|
33
|
+
const CALL_NAME_ATTR = 'weflayr.call_name';
|
|
34
|
+
exports.CALL_NAME_ATTR = CALL_NAME_ATTR;
|
|
35
|
+
const BILLING_PROVIDER_ATTR = 'weflayr.billing_provider';
|
|
36
|
+
exports.BILLING_PROVIDER_ATTR = BILLING_PROVIDER_ATTR;
|
|
32
37
|
const UUID_ATTR = 'weflayr.uuid';
|
|
33
38
|
exports.UUID_ATTR = UUID_ATTR;
|
|
34
39
|
// Prefix for user-supplied `extraTags` so downstream normalizers can pick
|
|
@@ -50,53 +55,64 @@ function _buildAttributes({ featureName, customerName, providerNameOverride, ext
|
|
|
50
55
|
attributes[`${EXTRA_TAG_PREFIX}${key}`] = value;
|
|
51
56
|
}
|
|
52
57
|
if (providerNameOverride !== undefined && providerNameOverride !== null) {
|
|
53
|
-
attributes[
|
|
58
|
+
attributes[BILLING_PROVIDER_ATTR] = providerNameOverride;
|
|
54
59
|
}
|
|
55
60
|
return attributes;
|
|
56
61
|
}
|
|
62
|
+
// The shape of the uuid createFeatureRunUuid returns, which a uuid passed to
|
|
63
|
+
// propagateMetadata must have too, so every feature run is named the same way.
|
|
64
|
+
const FEATURE_RUN_UUID_PATTERN = /^[0-9a-f]{32}$/;
|
|
65
|
+
/**
|
|
66
|
+
* A new feature run uuid, the kind `propagateMetadata` creates for each scope,
|
|
67
|
+
* e.g. `"3f2b9c1e0d4a4b6f8e7d5c4b3a291807"`.
|
|
68
|
+
*
|
|
69
|
+
* Create one where a run starts and pass it as `featureRunUuid` to every
|
|
70
|
+
* `propagateMetadata` scope of the run, including the ones opened in other
|
|
71
|
+
* processes, so all its calls are recorded under one feature run.
|
|
72
|
+
*/
|
|
73
|
+
function createFeatureRunUuid() {
|
|
74
|
+
return (0, node_crypto_1.randomUUID)().replace(/-/g, '');
|
|
75
|
+
}
|
|
57
76
|
function propagateMetadata(options, fn) {
|
|
58
|
-
const { featureName,
|
|
59
|
-
let { customerName, providerNameOverride } = options ?? {};
|
|
60
|
-
if (customerId != null) {
|
|
61
|
-
process.emitWarning('propagateMetadata: `customerId` is deprecated, use `customerName`', 'DeprecationWarning');
|
|
62
|
-
customerName = customerName ?? customerId;
|
|
63
|
-
}
|
|
64
|
-
if (providerName != null) {
|
|
65
|
-
process.emitWarning('propagateMetadata: `providerName` is deprecated, use `providerNameOverride`', 'DeprecationWarning');
|
|
66
|
-
providerNameOverride = providerNameOverride ?? providerName;
|
|
67
|
-
}
|
|
77
|
+
const { featureName, customerName } = options ?? {};
|
|
68
78
|
if (featureName == null || customerName == null) {
|
|
69
79
|
throw new Error('propagateMetadata: `featureName` and `customerName` are required');
|
|
70
80
|
}
|
|
71
|
-
|
|
81
|
+
if (options.featureRunUuid !== undefined &&
|
|
82
|
+
!FEATURE_RUN_UUID_PATTERN.test(options.featureRunUuid)) {
|
|
83
|
+
throw new Error('propagateMetadata: `featureRunUuid` must be 32 lowercase hexadecimal characters, ' +
|
|
84
|
+
`as createFeatureRunUuid() returns, not "${options.featureRunUuid}"`);
|
|
85
|
+
}
|
|
72
86
|
if (typeof fn === 'function') {
|
|
73
|
-
return _runWithMetadata(
|
|
87
|
+
return _runWithMetadata(options, fn);
|
|
74
88
|
}
|
|
75
89
|
return function decorator(target, _key, descriptor) {
|
|
76
90
|
if (descriptor && typeof descriptor.value === 'function') {
|
|
77
91
|
const original = descriptor.value;
|
|
78
92
|
descriptor.value = function wrapped(...args) {
|
|
79
|
-
return _runWithMetadata(
|
|
93
|
+
return _runWithMetadata(options, () => original.apply(this, args));
|
|
80
94
|
};
|
|
81
95
|
return descriptor;
|
|
82
96
|
}
|
|
83
97
|
if (typeof target === 'function') {
|
|
84
98
|
return function wrapped(...args) {
|
|
85
|
-
return _runWithMetadata(
|
|
99
|
+
return _runWithMetadata(options, () => target.apply(this, args));
|
|
86
100
|
};
|
|
87
101
|
}
|
|
88
102
|
return target;
|
|
89
103
|
};
|
|
90
104
|
}
|
|
91
105
|
function _runWithMetadata(options, fn) {
|
|
92
|
-
const { featureName, customerName, providerNameOverride, extraTags } = options;
|
|
93
|
-
|
|
106
|
+
const { featureName, customerName, providerNameOverride, extraTags, featureRunUuid } = options;
|
|
107
|
+
return _runWithAttributes(_buildAttributes({
|
|
94
108
|
featureName,
|
|
95
109
|
customerName,
|
|
96
110
|
providerNameOverride,
|
|
97
111
|
extraTags: extraTags ?? {},
|
|
98
|
-
spanUuid:
|
|
99
|
-
});
|
|
112
|
+
spanUuid: featureRunUuid ?? createFeatureRunUuid(),
|
|
113
|
+
}), fn);
|
|
114
|
+
}
|
|
115
|
+
function _runWithAttributes(attributes, fn) {
|
|
100
116
|
const parent = api_1.context.active();
|
|
101
117
|
const merged = { ...getMergedMetadataFromContext(parent), ...attributes };
|
|
102
118
|
const newContext = parent.setValue(WEFLAYR_METADATA_CONTEXT_KEY, merged);
|
|
@@ -108,3 +124,27 @@ function _runWithMetadata(options, fn) {
|
|
|
108
124
|
}
|
|
109
125
|
return api_1.context.with(newContext, fn);
|
|
110
126
|
}
|
|
127
|
+
function setCallName(callName, fn) {
|
|
128
|
+
if (!callName) {
|
|
129
|
+
throw new Error('setCallName: `callName` must not be empty');
|
|
130
|
+
}
|
|
131
|
+
const attributes = { [CALL_NAME_ATTR]: callName };
|
|
132
|
+
if (typeof fn === 'function') {
|
|
133
|
+
return _runWithAttributes(attributes, fn);
|
|
134
|
+
}
|
|
135
|
+
return function decorator(target, _key, descriptor) {
|
|
136
|
+
if (descriptor && typeof descriptor.value === 'function') {
|
|
137
|
+
const original = descriptor.value;
|
|
138
|
+
descriptor.value = function wrapped(...args) {
|
|
139
|
+
return _runWithAttributes(attributes, () => original.apply(this, args));
|
|
140
|
+
};
|
|
141
|
+
return descriptor;
|
|
142
|
+
}
|
|
143
|
+
if (typeof target === 'function') {
|
|
144
|
+
return function wrapped(...args) {
|
|
145
|
+
return _runWithAttributes(attributes, () => target.apply(this, args));
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
return target;
|
|
149
|
+
};
|
|
150
|
+
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
import type { ReadableSpan } from '@opentelemetry/sdk-trace-base';
|
|
2
|
+
export declare const SENT_TO_WEFLAYR_GATEWAY_ATTR = "weflayr.sent_to_weflayr_gateway";
|
|
2
3
|
export declare function isKnownLLMInstrumentor(span: ReadableSpan): boolean;
|
|
4
|
+
export declare function wasSentToWeflayrGateway(span: ReadableSpan): boolean;
|
|
3
5
|
export declare function isDefaultExportSpan(span: ReadableSpan): boolean;
|
|
@@ -15,8 +15,14 @@
|
|
|
15
15
|
//
|
|
16
16
|
// See LICENSE-MIT in the package root for the full license text.
|
|
17
17
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
exports.SENT_TO_WEFLAYR_GATEWAY_ATTR = void 0;
|
|
18
19
|
exports.isKnownLLMInstrumentor = isKnownLLMInstrumentor;
|
|
20
|
+
exports.wasSentToWeflayrGateway = wasSentToWeflayrGateway;
|
|
19
21
|
exports.isDefaultExportSpan = isDefaultExportSpan;
|
|
22
|
+
// Stamped by `enablePropagateMetadataToGateway` on the span of a call sent to
|
|
23
|
+
// the Weflayr AI gateway. The gateway sends that call's telemetry itself, so
|
|
24
|
+
// exporting the span as well would store the same call twice.
|
|
25
|
+
exports.SENT_TO_WEFLAYR_GATEWAY_ATTR = 'weflayr.sent_to_weflayr_gateway';
|
|
20
26
|
const KNOWN_LLM_INSTRUMENTATION_SCOPE_PREFIXES = [
|
|
21
27
|
'agent_framework',
|
|
22
28
|
'ai',
|
|
@@ -56,6 +62,9 @@ function isKnownLLMInstrumentor(span) {
|
|
|
56
62
|
const scope = span.instrumentationScope.name;
|
|
57
63
|
return KNOWN_LLM_INSTRUMENTATION_SCOPE_PREFIXES.some((prefix) => scope === prefix || scope.startsWith(`${prefix}.`));
|
|
58
64
|
}
|
|
65
|
+
function wasSentToWeflayrGateway(span) {
|
|
66
|
+
return Boolean(span.attributes[exports.SENT_TO_WEFLAYR_GATEWAY_ATTR]);
|
|
67
|
+
}
|
|
59
68
|
function isDefaultExportSpan(span) {
|
|
60
|
-
return isKnownLLMInstrumentor(span);
|
|
69
|
+
return isKnownLLMInstrumentor(span) && !wasSentToWeflayrGateway(span);
|
|
61
70
|
}
|
|
@@ -6,7 +6,7 @@ export type Tags = Record<string, string | number | boolean>;
|
|
|
6
6
|
/**
|
|
7
7
|
* Span attributes the mask callback can read or scrub. The OpenTelemetry GenAI
|
|
8
8
|
* semantic-convention keys get autocomplete; the index signature catches user
|
|
9
|
-
* tags and provider-specific attributes (e.g.
|
|
9
|
+
* tags and provider-specific attributes (e.g. `weflayr.*`).
|
|
10
10
|
*/
|
|
11
11
|
export interface OtelSpanAttributes {
|
|
12
12
|
'gen_ai.system'?: string;
|
|
@@ -43,7 +43,6 @@ export declare class WeflayrSpanProcessor implements SpanProcessor {
|
|
|
43
43
|
private _mask?;
|
|
44
44
|
private _maskFunctionSource;
|
|
45
45
|
private _shouldExportSpan;
|
|
46
|
-
private _vercelAiSdkSpanFilter;
|
|
47
46
|
private _openLlmSpans;
|
|
48
47
|
constructor({ exporter, exportMode, defaultTags, mask, shouldExportSpan, }: WeflayrSpanProcessorOptions);
|
|
49
48
|
onStart(span: StartedSpan, parentContext: Context): void;
|
|
@@ -20,7 +20,7 @@ const api_1 = require("@opentelemetry/api");
|
|
|
20
20
|
const sdk_trace_base_1 = require("@opentelemetry/sdk-trace-base");
|
|
21
21
|
const propagation_1 = require("./propagation");
|
|
22
22
|
const span_filter_1 = require("./span-filter");
|
|
23
|
-
const
|
|
23
|
+
const strip_media_payloads_1 = require("./strip-media-payloads");
|
|
24
24
|
const logger_1 = require("../logger");
|
|
25
25
|
// setup({captureMessageContent: ...}) records the choice in this env var (the
|
|
26
26
|
// official OTel GenAI one). Instrumentors gate content on their own knobs
|
|
@@ -30,24 +30,25 @@ const logger_1 = require("../logger");
|
|
|
30
30
|
exports.CONTENT_CAPTURE_ENV = 'OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT';
|
|
31
31
|
// The new-semconv JSON message attributes and the legacy indexed
|
|
32
32
|
// `gen_ai.prompt.{i}.*` / `gen_ai.completion.{i}.*` attributes - plus the
|
|
33
|
-
// request's tool definitions and structured-output schema
|
|
34
|
-
//
|
|
35
|
-
// way the messages are.
|
|
33
|
+
// request's tool definitions and structured-output schema: their descriptions
|
|
34
|
+
// are prompt content the same way the messages are.
|
|
36
35
|
const CONTENT_ATTRIBUTES = [
|
|
37
36
|
'gen_ai.input.messages',
|
|
38
37
|
'gen_ai.output.messages',
|
|
39
38
|
'gen_ai.system_instructions',
|
|
40
39
|
'gen_ai.tool.definitions',
|
|
41
40
|
'gen_ai.request.structured_output_schema',
|
|
42
|
-
// The
|
|
43
|
-
|
|
44
|
-
'
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
'
|
|
48
|
-
'
|
|
41
|
+
// The embedded texts of weflayr's own embeddings instrumentors.
|
|
42
|
+
'weflayr.embeddings.input',
|
|
43
|
+
// A provider-run web search: the tool's own description, the queries the
|
|
44
|
+
// answer ran and the pages it read. weflayr.web_search.requests is NOT
|
|
45
|
+
// stripped: it is how many searches the call is billed for, not content.
|
|
46
|
+
'weflayr.web_search.tool_definitions',
|
|
47
|
+
'weflayr.web_search.output_blocks',
|
|
48
|
+
'weflayr.web_search.options',
|
|
49
|
+
'weflayr.web_search.citations',
|
|
49
50
|
];
|
|
50
|
-
const CONTENT_ATTRIBUTE_PREFIXES = ['gen_ai.prompt.', 'gen_ai.completion.'
|
|
51
|
+
const CONTENT_ATTRIBUTE_PREFIXES = ['gen_ai.prompt.', 'gen_ai.completion.'];
|
|
51
52
|
function _isContentAttribute(key) {
|
|
52
53
|
return (CONTENT_ATTRIBUTES.includes(key) ||
|
|
53
54
|
CONTENT_ATTRIBUTE_PREFIXES.some((prefix) => key.startsWith(prefix)));
|
|
@@ -62,7 +63,6 @@ class WeflayrSpanProcessor {
|
|
|
62
63
|
_mask;
|
|
63
64
|
_maskFunctionSource;
|
|
64
65
|
_shouldExportSpan;
|
|
65
|
-
_vercelAiSdkSpanFilter;
|
|
66
66
|
// LLM spans we've started but not yet seen end. On forceFlush we end any
|
|
67
67
|
// leftovers with ERROR status — covers cases like openllmetry's anthropic
|
|
68
68
|
// streaming wrapper, which leaks the span when the underlying call
|
|
@@ -80,7 +80,6 @@ class WeflayrSpanProcessor {
|
|
|
80
80
|
this._mask = mask;
|
|
81
81
|
this._maskFunctionSource = mask ? String(mask) : null;
|
|
82
82
|
this._shouldExportSpan = shouldExportSpan ?? span_filter_1.isDefaultExportSpan;
|
|
83
|
-
this._vercelAiSdkSpanFilter = new vercel_ai_sdk_span_filter_1.VercelAiSdkSpanFilter();
|
|
84
83
|
this._openLlmSpans = new Set();
|
|
85
84
|
}
|
|
86
85
|
onStart(span, parentContext) {
|
|
@@ -94,7 +93,6 @@ class WeflayrSpanProcessor {
|
|
|
94
93
|
if ((0, span_filter_1.isKnownLLMInstrumentor)(span)) {
|
|
95
94
|
this._openLlmSpans.add(span);
|
|
96
95
|
}
|
|
97
|
-
this._vercelAiSdkSpanFilter.onStart(span);
|
|
98
96
|
(0, logger_1.debug)('span start: name=%s defaultTags=%d propagatedMetadata=%d', span.name, Object.keys(this._defaultTags).length, Object.keys(propagated).length);
|
|
99
97
|
return this._delegate.onStart(span, parentContext);
|
|
100
98
|
}
|
|
@@ -102,11 +100,7 @@ class WeflayrSpanProcessor {
|
|
|
102
100
|
// Same concrete object stored in onStart, viewed here as a ReadableSpan.
|
|
103
101
|
this._openLlmSpans.delete(span);
|
|
104
102
|
try {
|
|
105
|
-
|
|
106
|
-
if (vercelVerdict === true) {
|
|
107
|
-
(0, vercel_ai_sdk_span_filter_1.stampVercelGatewayProviderName)(span);
|
|
108
|
-
}
|
|
109
|
-
if (vercelVerdict === false || (vercelVerdict === null && !this._shouldExportSpan(span))) {
|
|
103
|
+
if (!this._shouldExportSpan(span)) {
|
|
110
104
|
(0, logger_1.debug)('span end: filtered out name=%s scope=%s', span.name, span.instrumentationScope?.name);
|
|
111
105
|
return;
|
|
112
106
|
}
|
|
@@ -121,6 +115,9 @@ class WeflayrSpanProcessor {
|
|
|
121
115
|
delete span.attributes[key];
|
|
122
116
|
}
|
|
123
117
|
}
|
|
118
|
+
else {
|
|
119
|
+
(0, strip_media_payloads_1.stripMediaPayloads)(span.attributes);
|
|
120
|
+
}
|
|
124
121
|
if (this._mask) {
|
|
125
122
|
try {
|
|
126
123
|
const masked = this._mask(span.attributes);
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Remove media payloads (base64 images, audio, documents) from the message
|
|
3
|
+
* content span attributes, right before export.
|
|
4
|
+
*
|
|
5
|
+
* An inline image, audio clip or document reaches the span in one of five shapes,
|
|
6
|
+
* depending on the instrumentor: a `blob` part carrying the base64 bytes in
|
|
7
|
+
* `content` (OpenAI chat completions, Anthropic), a `uri` part whose uri is a
|
|
8
|
+
* `data:` URI holding the same bytes (OpenAI Responses), Anthropic's own content
|
|
9
|
+
* block with the bytes under `source.data` (Bedrock), an OpenAI `file` block
|
|
10
|
+
* with the bytes under `file.file_data`, an OpenAI Responses `input_audio` block
|
|
11
|
+
* kept whole under `content` with the bytes under `content.input_audio.data`. A single
|
|
12
|
+
* image is hundreds of kilobytes, so keeping it would make every event on the
|
|
13
|
+
* queue that much bigger for nothing: the payload is never read back.
|
|
14
|
+
*
|
|
15
|
+
* Each of those becomes `{"type": "blob", "mime_type": "<mime_type>"}`, the same
|
|
16
|
+
* byte-less stub the google, cohere and bedrock instrumentation complements
|
|
17
|
+
* build themselves: the analyses reading these messages still see that the call
|
|
18
|
+
* carried content the stored text does not. A `uri` part pointing at a hosted
|
|
19
|
+
* image is a reference, not a payload, and is kept as is.
|
|
20
|
+
*/
|
|
21
|
+
import type { OtelSpanAttributes } from './span-processor';
|
|
22
|
+
/**
|
|
23
|
+
* Rewrite in place every message content attribute of `attributes` that carries
|
|
24
|
+
* media bytes.
|
|
25
|
+
*/
|
|
26
|
+
export declare function stripMediaPayloads(attributes: OtelSpanAttributes): void;
|