weflayr 0.23.0 → 0.26.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 +2 -2
- package/dist/{api → src/api}/generated/facade.gen.d.ts +163 -12
- package/dist/{api → src/api}/generated/facade.gen.js +10 -2
- package/dist/src/api/generated/index.d.ts +2 -0
- package/dist/{api → src/api}/generated/index.js +9 -1
- package/dist/{api → src/api}/generated/sdk.gen.d.ts +35 -3
- package/dist/{api → src/api}/generated/sdk.gen.js +88 -4
- package/{src/api/generated/types.gen.ts → dist/src/api/generated/types.gen.d.ts} +692 -116
- package/dist/{api → src/api}/makeEndpoint.js +1 -3
- package/dist/src/auto-instrument.d.ts +42 -0
- package/dist/src/auto-instrument.js +156 -0
- package/dist/src/index.d.ts +9 -0
- package/dist/src/index.js +49 -0
- package/dist/src/instrumentation/anthropic/get_cache_and_reasoning_telemetry.d.ts +20 -0
- package/dist/src/instrumentation/anthropic/get_cache_and_reasoning_telemetry.js +114 -0
- package/dist/src/instrumentation/anthropic/get_cache_config_telemetry.d.ts +28 -0
- package/dist/src/instrumentation/anthropic/get_cache_config_telemetry.js +92 -0
- package/dist/src/instrumentation/anthropic/get_streaming_accumulation_telemetry.d.ts +25 -0
- package/dist/src/instrumentation/anthropic/get_streaming_accumulation_telemetry.js +141 -0
- package/dist/src/instrumentation/anthropic/get_structured_output_schema_telemetry.d.ts +9 -0
- package/dist/src/instrumentation/anthropic/get_structured_output_schema_telemetry.js +17 -0
- package/dist/src/instrumentation/anthropic/get_tool_definitions_telemetry.d.ts +15 -0
- package/dist/src/instrumentation/anthropic/get_tool_definitions_telemetry.js +50 -0
- package/dist/src/instrumentation/anthropic/set_telemetry_attributes.d.ts +15 -0
- package/dist/src/instrumentation/anthropic/set_telemetry_attributes.js +39 -0
- package/dist/src/instrumentation/bedrock/get_cache_config_telemetry.d.ts +22 -0
- package/dist/src/instrumentation/bedrock/get_cache_config_telemetry.js +97 -0
- package/dist/src/instrumentation/bedrock/get_cache_telemetry.d.ts +22 -0
- package/dist/src/instrumentation/bedrock/get_cache_telemetry.js +102 -0
- package/dist/src/instrumentation/bedrock/get_input_message_content_telemetry.d.ts +15 -0
- package/dist/src/instrumentation/bedrock/get_input_message_content_telemetry.js +91 -0
- package/dist/src/instrumentation/bedrock/get_model_id_telemetry.d.ts +21 -0
- package/dist/src/instrumentation/bedrock/get_model_id_telemetry.js +54 -0
- package/dist/src/instrumentation/bedrock/get_reasoning_output_telemetry.d.ts +19 -0
- package/dist/src/instrumentation/bedrock/get_reasoning_output_telemetry.js +81 -0
- package/dist/src/instrumentation/bedrock/get_streaming_output_message_telemetry.d.ts +27 -0
- package/dist/src/instrumentation/bedrock/get_streaming_output_message_telemetry.js +126 -0
- package/dist/src/instrumentation/bedrock/get_tool_call_output_telemetry.d.ts +17 -0
- package/dist/src/instrumentation/bedrock/get_tool_call_output_telemetry.js +85 -0
- package/dist/src/instrumentation/bedrock/get_tool_definitions_telemetry.d.ts +15 -0
- package/dist/src/instrumentation/bedrock/get_tool_definitions_telemetry.js +68 -0
- package/dist/src/instrumentation/bedrock/set_telemetry_attributes.d.ts +18 -0
- package/dist/src/instrumentation/bedrock/set_telemetry_attributes.js +47 -0
- package/dist/src/instrumentation/cohere/get_chat_telemetry.d.ts +23 -0
- package/dist/src/instrumentation/cohere/get_chat_telemetry.js +77 -0
- package/dist/src/instrumentation/cohere/get_embeddings_telemetry.d.ts +18 -0
- package/dist/src/instrumentation/cohere/get_embeddings_telemetry.js +88 -0
- package/dist/src/instrumentation/cohere/get_structured_output_schema_telemetry.d.ts +12 -0
- package/dist/src/instrumentation/cohere/get_structured_output_schema_telemetry.js +18 -0
- package/dist/src/instrumentation/cohere/get_tool_call_output_telemetry.d.ts +31 -0
- package/dist/src/instrumentation/cohere/get_tool_call_output_telemetry.js +106 -0
- package/dist/src/instrumentation/cohere/get_tool_definitions_telemetry.d.ts +27 -0
- package/dist/src/instrumentation/cohere/get_tool_definitions_telemetry.js +75 -0
- package/dist/src/instrumentation/cohere/set_telemetry_attributes.d.ts +17 -0
- package/dist/src/instrumentation/cohere/set_telemetry_attributes.js +44 -0
- package/dist/src/instrumentation/elevenlabs/full_instrumentation.d.ts +28 -0
- package/dist/src/instrumentation/elevenlabs/full_instrumentation.js +209 -0
- package/dist/src/instrumentation/google/get_audio_telemetry.d.ts +9 -0
- package/dist/src/instrumentation/google/get_audio_telemetry.js +28 -0
- package/dist/src/instrumentation/google/get_cache_operations_telemetry.d.ts +23 -0
- package/dist/src/instrumentation/google/get_cache_operations_telemetry.js +154 -0
- package/dist/src/instrumentation/google/get_cache_telemetry.d.ts +16 -0
- package/dist/src/instrumentation/google/get_cache_telemetry.js +38 -0
- package/dist/src/instrumentation/google/get_embeddings_telemetry.d.ts +12 -0
- package/dist/src/instrumentation/google/get_embeddings_telemetry.js +85 -0
- package/dist/src/instrumentation/google/get_provider_name.d.ts +11 -0
- package/{src → dist/src}/instrumentation/google/get_provider_name.js +11 -12
- package/{src/instrumentation/google/get_reasoning_telemetry.js → dist/src/instrumentation/google/get_reasoning_telemetry.d.ts} +1 -11
- package/dist/src/instrumentation/google/get_reasoning_telemetry.js +21 -0
- package/dist/src/instrumentation/google/get_structured_output_schema_telemetry.d.ts +12 -0
- package/dist/src/instrumentation/google/get_structured_output_schema_telemetry.js +19 -0
- package/dist/src/instrumentation/google/set_telemetry_attributes.d.ts +18 -0
- package/dist/src/instrumentation/google/set_telemetry_attributes.js +98 -0
- package/dist/src/instrumentation/openai/get_audio_telemetry.d.ts +25 -0
- package/dist/src/instrumentation/openai/get_audio_telemetry.js +123 -0
- package/dist/src/instrumentation/openai/get_bedrock_mantle_telemetry.d.ts +35 -0
- package/dist/src/instrumentation/openai/get_bedrock_mantle_telemetry.js +148 -0
- package/dist/src/instrumentation/openai/get_cache_and_reasoning_telemetry.d.ts +27 -0
- package/dist/src/instrumentation/openai/get_cache_and_reasoning_telemetry.js +72 -0
- package/dist/src/instrumentation/openai/get_cache_config_telemetry.d.ts +28 -0
- package/dist/src/instrumentation/openai/get_cache_config_telemetry.js +91 -0
- package/dist/src/instrumentation/openai/get_embeddings_telemetry.d.ts +18 -0
- package/dist/src/instrumentation/openai/get_embeddings_telemetry.js +86 -0
- package/dist/src/instrumentation/openai/get_streaming_usage.d.ts +21 -0
- package/dist/src/instrumentation/openai/get_streaming_usage.js +74 -0
- package/dist/src/instrumentation/openai/get_structured_output_schema_telemetry.d.ts +12 -0
- package/dist/src/instrumentation/openai/get_structured_output_schema_telemetry.js +20 -0
- package/dist/src/instrumentation/openai/set_telemetry_attributes.d.ts +17 -0
- package/dist/src/instrumentation/openai/set_telemetry_attributes.js +47 -0
- package/dist/src/instrumentation/shared/get_structured_output_schema_telemetry.d.ts +35 -0
- package/dist/src/instrumentation/shared/get_structured_output_schema_telemetry.js +76 -0
- package/dist/src/instrumentation/vercel/full_instrumentation.d.ts +3 -0
- package/dist/src/instrumentation/vercel/full_instrumentation.js +54 -0
- package/dist/src/instrumentation/vercel/get_cache_creation_telemetry.d.ts +22 -0
- package/dist/src/instrumentation/vercel/get_cache_creation_telemetry.js +66 -0
- package/dist/src/instrumentation/vercel/get_structured_output_schema_telemetry.d.ts +24 -0
- package/dist/src/instrumentation/vercel/get_structured_output_schema_telemetry.js +84 -0
- package/dist/src/logger.d.ts +1 -0
- package/dist/src/logger.js +6 -0
- package/dist/src/otel/exporter.d.ts +19 -0
- package/dist/src/otel/exporter.js +94 -0
- package/dist/src/otel/propagation.d.ts +46 -0
- package/dist/src/otel/propagation.js +110 -0
- package/dist/src/otel/span-filter.d.ts +3 -0
- package/dist/src/otel/span-filter.js +61 -0
- package/dist/src/otel/span-processor.d.ts +55 -0
- package/dist/src/otel/span-processor.js +169 -0
- package/dist/src/otel/vercel-ai-sdk-span-filter.d.ts +12 -0
- package/dist/src/otel/vercel-ai-sdk-span-filter.js +88 -0
- package/dist/src/setup.d.ts +50 -0
- package/dist/src/setup.js +155 -0
- package/package.json +11 -5
- package/.prettierignore +0 -2
- package/.prettierrc.json +0 -7
- package/LICENSE-MIT +0 -21
- package/NOTICE +0 -12
- package/dist/api/generated/index.d.ts +0 -2
- package/dist/api/generated/types.gen.d.ts +0 -749
- package/eslint.config.mjs +0 -36
- package/index.d.ts +0 -167
- package/openapi-ts.config.mjs +0 -9
- package/scripts/generate_facade.mjs +0 -86
- package/src/api/caseConversion.ts +0 -58
- package/src/api/generated/client/client.gen.ts +0 -277
- package/src/api/generated/client/index.ts +0 -27
- package/src/api/generated/client/types.gen.ts +0 -218
- package/src/api/generated/client/utils.gen.ts +0 -316
- package/src/api/generated/client.gen.ts +0 -16
- package/src/api/generated/core/auth.gen.ts +0 -48
- package/src/api/generated/core/bodySerializer.gen.ts +0 -82
- package/src/api/generated/core/params.gen.ts +0 -178
- package/src/api/generated/core/pathSerializer.gen.ts +0 -171
- package/src/api/generated/core/queryKeySerializer.gen.ts +0 -117
- package/src/api/generated/core/serverSentEvents.gen.ts +0 -242
- package/src/api/generated/core/types.gen.ts +0 -110
- package/src/api/generated/core/utils.gen.ts +0 -140
- package/src/api/generated/facade.gen.ts +0 -68
- package/src/api/generated/index.ts +0 -4
- package/src/api/generated/sdk.gen.ts +0 -121
- package/src/api/index.ts +0 -45
- package/src/api/makeEndpoint.ts +0 -54
- package/src/auto-instrument.js +0 -171
- package/src/index.js +0 -19
- package/src/instrumentation/anthropic/get_cache_and_reasoning_telemetry.js +0 -107
- package/src/instrumentation/anthropic/get_streaming_stop_reason_telemetry.js +0 -121
- package/src/instrumentation/anthropic/get_tool_definitions_telemetry.js +0 -50
- package/src/instrumentation/anthropic/set_telemetry_attributes.js +0 -37
- package/src/instrumentation/bedrock/get_cache_telemetry.js +0 -104
- package/src/instrumentation/bedrock/get_input_message_content_telemetry.js +0 -94
- package/src/instrumentation/bedrock/get_streaming_output_message_telemetry.js +0 -87
- package/src/instrumentation/bedrock/set_telemetry_attributes.js +0 -36
- package/src/instrumentation/cohere/get_chat_telemetry.js +0 -88
- package/src/instrumentation/cohere/get_embeddings_telemetry.js +0 -107
- package/src/instrumentation/cohere/set_telemetry_attributes.js +0 -32
- package/src/instrumentation/elevenlabs/full_instrumentation.js +0 -236
- package/src/instrumentation/google/get_audio_telemetry.js +0 -30
- package/src/instrumentation/google/get_cache_telemetry.js +0 -25
- package/src/instrumentation/google/get_embeddings_telemetry.js +0 -99
- package/src/instrumentation/google/set_telemetry_attributes.js +0 -98
- package/src/instrumentation/openai/get_audio_telemetry.js +0 -149
- package/src/instrumentation/openai/get_cache_and_reasoning_telemetry.js +0 -71
- package/src/instrumentation/openai/get_embeddings_telemetry.js +0 -107
- package/src/instrumentation/openai/get_streaming_usage.js +0 -81
- package/src/instrumentation/openai/set_telemetry_attributes.js +0 -39
- package/src/instrumentation/vercel/full_instrumentation.js +0 -44
- package/src/logger.js +0 -8
- package/src/otel/exporter.js +0 -108
- package/src/otel/propagation.js +0 -133
- package/src/otel/span-filter.js +0 -69
- package/src/otel/span-processor.js +0 -162
- package/src/otel/vercel-ai-sdk-span-filter.js +0 -79
- package/src/setup.js +0 -167
- package/tests/api/index.test.js +0 -437
- package/tests/index.test.js +0 -824
- package/tests/instrumentation/anthropic/get_cache_and_reasoning_telemetry.test.js +0 -158
- package/tests/instrumentation/anthropic/get_streaming_stop_reason_telemetry.test.js +0 -117
- package/tests/instrumentation/anthropic/get_tool_definitions_telemetry.test.js +0 -82
- package/tests/instrumentation/bedrock/get_cache_telemetry.test.js +0 -102
- package/tests/instrumentation/bedrock/get_input_message_content_telemetry.test.js +0 -89
- package/tests/instrumentation/bedrock/get_streaming_output_message_telemetry.test.js +0 -87
- package/tests/instrumentation/cohere/get_chat_telemetry.test.js +0 -148
- package/tests/instrumentation/cohere/get_embeddings_telemetry.test.js +0 -132
- package/tests/instrumentation/elevenlabs/full_instrumentation.test.js +0 -258
- package/tests/instrumentation/google/get_embeddings_telemetry.test.js +0 -128
- package/tests/instrumentation/google/get_reasoning_telemetry.test.js +0 -41
- package/tests/instrumentation/google/set_telemetry_attributes.test.js +0 -219
- package/tests/instrumentation/openai/get_audio_telemetry.test.js +0 -170
- package/tests/instrumentation/openai/get_cache_and_reasoning_telemetry.test.js +0 -83
- package/tests/instrumentation/openai/get_embeddings_telemetry.test.js +0 -145
- package/tests/instrumentation/openai/get_streaming_usage.test.js +0 -105
- package/tests/instrumentation/vercel/full_instrumentation.test.js +0 -16
- package/tests/otel/vercel-ai-sdk-span-filter.test.js +0 -159
- package/tsconfig.json +0 -18
- /package/dist/{api → src/api}/caseConversion.d.ts +0 -0
- /package/dist/{api → src/api}/caseConversion.js +0 -0
- /package/dist/{api → src/api}/generated/client/client.gen.d.ts +0 -0
- /package/dist/{api → src/api}/generated/client/client.gen.js +0 -0
- /package/dist/{api → src/api}/generated/client/index.d.ts +0 -0
- /package/dist/{api → src/api}/generated/client/index.js +0 -0
- /package/dist/{api → src/api}/generated/client/types.gen.d.ts +0 -0
- /package/dist/{api → src/api}/generated/client/types.gen.js +0 -0
- /package/dist/{api → src/api}/generated/client/utils.gen.d.ts +0 -0
- /package/dist/{api → src/api}/generated/client/utils.gen.js +0 -0
- /package/dist/{api → src/api}/generated/client.gen.d.ts +0 -0
- /package/dist/{api → src/api}/generated/client.gen.js +0 -0
- /package/dist/{api → src/api}/generated/core/auth.gen.d.ts +0 -0
- /package/dist/{api → src/api}/generated/core/auth.gen.js +0 -0
- /package/dist/{api → src/api}/generated/core/bodySerializer.gen.d.ts +0 -0
- /package/dist/{api → src/api}/generated/core/bodySerializer.gen.js +0 -0
- /package/dist/{api → src/api}/generated/core/params.gen.d.ts +0 -0
- /package/dist/{api → src/api}/generated/core/params.gen.js +0 -0
- /package/dist/{api → src/api}/generated/core/pathSerializer.gen.d.ts +0 -0
- /package/dist/{api → src/api}/generated/core/pathSerializer.gen.js +0 -0
- /package/dist/{api → src/api}/generated/core/queryKeySerializer.gen.d.ts +0 -0
- /package/dist/{api → src/api}/generated/core/queryKeySerializer.gen.js +0 -0
- /package/dist/{api → src/api}/generated/core/serverSentEvents.gen.d.ts +0 -0
- /package/dist/{api → src/api}/generated/core/serverSentEvents.gen.js +0 -0
- /package/dist/{api → src/api}/generated/core/types.gen.d.ts +0 -0
- /package/dist/{api → src/api}/generated/core/types.gen.js +0 -0
- /package/dist/{api → src/api}/generated/core/utils.gen.d.ts +0 -0
- /package/dist/{api → src/api}/generated/core/utils.gen.js +0 -0
- /package/dist/{api → src/api}/generated/types.gen.js +0 -0
- /package/dist/{api → src/api}/index.d.ts +0 -0
- /package/dist/{api → src/api}/index.js +0 -0
- /package/dist/{api → src/api}/makeEndpoint.d.ts +0 -0
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Per-modality audio token enrichment for the Google GenAI (Gemini) chat span.
|
|
5
|
-
*
|
|
6
|
-
* Neither instrumentor breaks usage down by modality, so for audio in/out (e.g.
|
|
7
|
-
* Gemini TTS) we read the response's per-modality token details and set
|
|
8
|
-
* `gen_ai.usage.{input,output}_tokens_details.audio_tokens`.
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
// Sum the token counts whose modality is AUDIO across a *TokensDetails array.
|
|
12
|
-
function _audioTokens(details) {
|
|
13
|
-
if (!Array.isArray(details)) return 0;
|
|
14
|
-
return details.reduce(
|
|
15
|
-
(total, detail) => total + (detail && detail.modality === 'AUDIO' ? detail.tokenCount || 0 : 0),
|
|
16
|
-
0
|
|
17
|
-
);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function setAudioTelemetry(span, response) {
|
|
21
|
-
const usage = response && response.usageMetadata;
|
|
22
|
-
if (!usage) return;
|
|
23
|
-
const inputAudio = _audioTokens(usage.promptTokensDetails);
|
|
24
|
-
const outputAudio = _audioTokens(usage.candidatesTokensDetails);
|
|
25
|
-
if (inputAudio) span.setAttribute('gen_ai.usage.input_tokens_details.audio_tokens', inputAudio);
|
|
26
|
-
if (outputAudio)
|
|
27
|
-
span.setAttribute('gen_ai.usage.output_tokens_details.audio_tokens', outputAudio);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
module.exports = { setAudioTelemetry, _audioTokens };
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Context-caching token enrichment for the Google GenAI (Gemini) chat span.
|
|
5
|
-
*
|
|
6
|
-
* Neither instrumentor records cached-content tokens, so we read
|
|
7
|
-
* `usageMetadata.cachedContentTokenCount` (explicit or implicit caching) and
|
|
8
|
-
* split it by modality: audio cached tokens go to
|
|
9
|
-
* `gen_ai.usage.input_tokens_details.cached_audio_tokens`and the remainder to `gen_ai.usage.cache_read_input_tokens`
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
const { _audioTokens } = require('./get_audio_telemetry');
|
|
13
|
-
|
|
14
|
-
function setCacheTelemetry(span, response) {
|
|
15
|
-
const usage = response && response.usageMetadata;
|
|
16
|
-
const cachedTokens = (usage && usage.cachedContentTokenCount) || 0;
|
|
17
|
-
if (!cachedTokens) return;
|
|
18
|
-
const cachedAudio = _audioTokens(usage.cacheTokensDetails);
|
|
19
|
-
if (cachedAudio)
|
|
20
|
-
span.setAttribute('gen_ai.usage.input_tokens_details.cached_audio_tokens', cachedAudio);
|
|
21
|
-
const cacheRead = Math.max(0, cachedTokens - cachedAudio);
|
|
22
|
-
if (cacheRead) span.setAttribute('gen_ai.usage.cache_read_input_tokens', cacheRead);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
module.exports = { setCacheTelemetry };
|
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Embeddings-endpoint telemetry for Google GenAI (Gemini).
|
|
5
|
-
*
|
|
6
|
-
* `@traceloop/instrumentation-google-generativeai` wraps generate_content but not
|
|
7
|
-
* embeddings, so this covers it by starting its own span on the private
|
|
8
|
-
* `Models.prototype.embedContentInternal` (which the public `embedContent`
|
|
9
|
-
* delegates to). The Gemini Developer API returns NO token usage on embedContent
|
|
10
|
-
* (only Vertex does), so the span records the billable unit weflayr can price:
|
|
11
|
-
* the input character count under `gen_ai.usage.input_characters` (normalized to
|
|
12
|
-
* num_input_chars).
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
const { trace, SpanStatusCode } = require('@opentelemetry/api');
|
|
16
|
-
|
|
17
|
-
const { getProviderName } = require('./get_provider_name');
|
|
18
|
-
|
|
19
|
-
const TRACER_NAME = 'weflayr.instrumentation.google.embeddings';
|
|
20
|
-
const EMBEDDINGS_OPERATION = 'embeddings';
|
|
21
|
-
|
|
22
|
-
// `contents` is a string, a Content/Part object, or an array of those; the
|
|
23
|
-
// billable unit is the total number of characters of text across them.
|
|
24
|
-
function _contentCharacters(contents) {
|
|
25
|
-
if (contents == null) return 0;
|
|
26
|
-
if (typeof contents === 'string') return contents.length;
|
|
27
|
-
if (Array.isArray(contents)) {
|
|
28
|
-
return contents.reduce((total, item) => total + _contentCharacters(item), 0);
|
|
29
|
-
}
|
|
30
|
-
if (typeof contents === 'object') {
|
|
31
|
-
if (typeof contents.text === 'string') return contents.text.length;
|
|
32
|
-
if (Array.isArray(contents.parts)) return _contentCharacters(contents.parts);
|
|
33
|
-
}
|
|
34
|
-
return 0;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// The embedded text mapped to the role+parts message shape the chat path uses:
|
|
38
|
-
// a string (or {text} / {parts} object) becomes one user message per content.
|
|
39
|
-
function _contentToMessage(content) {
|
|
40
|
-
if (typeof content === 'string') {
|
|
41
|
-
return { role: 'user', parts: [{ type: 'text', content }] };
|
|
42
|
-
}
|
|
43
|
-
if (content && typeof content.text === 'string') {
|
|
44
|
-
return { role: 'user', parts: [{ type: 'text', content: content.text }] };
|
|
45
|
-
}
|
|
46
|
-
return { role: 'user', parts: [] };
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function _recordError(span, error) {
|
|
50
|
-
span.setStatus({ code: SpanStatusCode.ERROR, message: error?.message ?? String(error) });
|
|
51
|
-
span.recordException(error);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// Wrap `Models.prototype.embedContentInternal`, pushing the restore tuple onto
|
|
55
|
-
// the shared `patched` array the orchestrator owns for uninstrument().
|
|
56
|
-
function instrumentEmbeddings(Models, patched) {
|
|
57
|
-
const owner = Models.prototype;
|
|
58
|
-
const original = owner.embedContentInternal;
|
|
59
|
-
if (typeof original !== 'function') return;
|
|
60
|
-
|
|
61
|
-
function wrapper(params, ...rest) {
|
|
62
|
-
const span = trace.getTracer(TRACER_NAME).startSpan(EMBEDDINGS_OPERATION);
|
|
63
|
-
span.setAttribute('gen_ai.system', getProviderName(this));
|
|
64
|
-
span.setAttribute('gen_ai.operation.name', EMBEDDINGS_OPERATION);
|
|
65
|
-
if (params && params.model) {
|
|
66
|
-
// The chat path strips the leading "models/" prefix; mirror it.
|
|
67
|
-
span.setAttribute('gen_ai.request.model', params.model.replace(/^models\//, ''));
|
|
68
|
-
}
|
|
69
|
-
span.setAttribute(
|
|
70
|
-
'gen_ai.usage.input_characters',
|
|
71
|
-
_contentCharacters(params && params.contents)
|
|
72
|
-
);
|
|
73
|
-
// The embedded text is the prompt; stamp it like the chat path does. The
|
|
74
|
-
// span processor strips it when message-content capture is disabled.
|
|
75
|
-
if (params && params.contents != null) {
|
|
76
|
-
const contentsList = Array.isArray(params.contents) ? params.contents : [params.contents];
|
|
77
|
-
span.setAttribute(
|
|
78
|
-
'gen_ai.input.messages',
|
|
79
|
-
JSON.stringify(contentsList.map(_contentToMessage))
|
|
80
|
-
);
|
|
81
|
-
}
|
|
82
|
-
return Promise.resolve(original.call(this, params, ...rest)).then(
|
|
83
|
-
(response) => {
|
|
84
|
-
span.end();
|
|
85
|
-
return response;
|
|
86
|
-
},
|
|
87
|
-
(error) => {
|
|
88
|
-
_recordError(span, error);
|
|
89
|
-
span.end();
|
|
90
|
-
throw error;
|
|
91
|
-
}
|
|
92
|
-
);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
owner.embedContentInternal = wrapper;
|
|
96
|
-
patched.push([owner, 'embedContentInternal', original]);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
module.exports = { instrumentEmbeddings };
|
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Google GenAI (Gemini) telemetry complement — the registered orchestrator.
|
|
5
|
-
*
|
|
6
|
-
* Wires every weflayr-supplied patch the traceloop Google instrumentor misses:
|
|
7
|
-
* - chat span enrichment (one wrap of generateContent{,Stream}Internal that
|
|
8
|
-
* stamps provider name + audio tokens + cache tokens — see the get_*_telemetry
|
|
9
|
-
* files), and
|
|
10
|
-
* - the embeddings endpoint it doesn't cover at all.
|
|
11
|
-
*
|
|
12
|
-
* The public arrows delegate to these private prototype methods while the
|
|
13
|
-
* traceloop span is the active span, so enrichment lands on that span.
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
const { trace } = require('@opentelemetry/api');
|
|
17
|
-
|
|
18
|
-
const { getProviderName } = require('./get_provider_name');
|
|
19
|
-
const { setAudioTelemetry } = require('./get_audio_telemetry');
|
|
20
|
-
const { setCacheTelemetry } = require('./get_cache_telemetry');
|
|
21
|
-
const { setReasoningTelemetry } = require('./get_reasoning_telemetry');
|
|
22
|
-
const { instrumentEmbeddings } = require('./get_embeddings_telemetry');
|
|
23
|
-
|
|
24
|
-
class GoogleTelemetryInstrumentor {
|
|
25
|
-
constructor() {
|
|
26
|
-
// [ownerPrototype, methodName, originalFunction] for uninstrument().
|
|
27
|
-
this._patched = [];
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
instrument() {
|
|
31
|
-
let Models;
|
|
32
|
-
try {
|
|
33
|
-
({ Models } = require('@google/genai'));
|
|
34
|
-
} catch (err) {
|
|
35
|
-
throw new Error(
|
|
36
|
-
'weflayr.autoInstrument(GOOGLE_GENAI): the @google/genai client is not installed. ' +
|
|
37
|
-
'Install it with: npm install @google/genai\n' +
|
|
38
|
-
`Underlying error: ${err.message}`
|
|
39
|
-
);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// Non-streaming: stamp provider up front, audio + cache tokens off the response.
|
|
43
|
-
this._wrapChat(Models.prototype, 'generateContentInternal', { stream: false });
|
|
44
|
-
// Streaming: stamp provider, then cache tokens off the final chunk's usageMetadata.
|
|
45
|
-
this._wrapChat(Models.prototype, 'generateContentStreamInternal', { stream: true });
|
|
46
|
-
// Embeddings: an endpoint the traceloop instrumentor doesn't cover at all.
|
|
47
|
-
instrumentEmbeddings(Models, this._patched);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
_wrapChat(owner, methodName, { stream }) {
|
|
51
|
-
const original = owner[methodName];
|
|
52
|
-
|
|
53
|
-
function wrapper(...args) {
|
|
54
|
-
const span = trace.getActiveSpan();
|
|
55
|
-
if (!span || !span.isRecording || !span.isRecording()) {
|
|
56
|
-
return original.apply(this, args);
|
|
57
|
-
}
|
|
58
|
-
span.setAttribute('gen_ai.system', getProviderName(this));
|
|
59
|
-
if (!stream) {
|
|
60
|
-
return Promise.resolve(original.apply(this, args)).then((response) => {
|
|
61
|
-
setAudioTelemetry(span, response);
|
|
62
|
-
setCacheTelemetry(span, response);
|
|
63
|
-
setReasoningTelemetry(span, response);
|
|
64
|
-
return response;
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
// Streamed: usage (incl. cachedContentTokenCount) lands on the final chunk;
|
|
68
|
-
// observe the chunks as they pass through. The instrumentor keeps the span
|
|
69
|
-
// open until the stream is exhausted.
|
|
70
|
-
return Promise.resolve(original.apply(this, args)).then((stream_) => {
|
|
71
|
-
if (!stream_ || typeof stream_[Symbol.asyncIterator] !== 'function') return stream_;
|
|
72
|
-
return (async function* observeCache() {
|
|
73
|
-
for await (const chunk of stream_) {
|
|
74
|
-
try {
|
|
75
|
-
setCacheTelemetry(span, chunk);
|
|
76
|
-
setReasoningTelemetry(span, chunk);
|
|
77
|
-
} catch {
|
|
78
|
-
// Never break the caller's stream over an enrichment failure.
|
|
79
|
-
}
|
|
80
|
-
yield chunk;
|
|
81
|
-
}
|
|
82
|
-
})();
|
|
83
|
-
});
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
owner[methodName] = wrapper;
|
|
87
|
-
this._patched.push([owner, methodName, original]);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
uninstrument() {
|
|
91
|
-
for (const [owner, methodName, original] of this._patched) {
|
|
92
|
-
owner[methodName] = original;
|
|
93
|
-
}
|
|
94
|
-
this._patched = [];
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
module.exports = { GoogleTelemetryInstrumentor };
|
|
@@ -1,149 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Hand-written OTel instrumentor for the OpenAI audio endpoints.
|
|
5
|
-
*
|
|
6
|
-
* The `@traceloop/instrumentation-openai` package wraps OpenAI's chat,
|
|
7
|
-
* completions, responses and image endpoints, but not the audio ones, so this
|
|
8
|
-
* ships inside weflayr to cover them. It wraps the Text-to-Speech and
|
|
9
|
-
* Speech-to-Text entrypoints to emit one `gen_ai.*` span per call, which flows
|
|
10
|
-
* through the standard span processor -> exporter with no new transport code. It
|
|
11
|
-
* is registered alongside the traceloop instrumentor whenever
|
|
12
|
-
* `autoInstrument({ aiSdks: 'openai' })` runs.
|
|
13
|
-
*
|
|
14
|
-
* The billable unit differs by API, so each span records the one OpenAI bills:
|
|
15
|
-
* - Text-to-Speech is billed per input character -> `gen_ai.usage.input_characters`.
|
|
16
|
-
* - Speech-to-Text is billed per second of audio -> `gen_ai.usage.input_seconds`.
|
|
17
|
-
*
|
|
18
|
-
* Both endpoints are request/response (unary): `audio.speech.create` resolves to
|
|
19
|
-
* the full audio and `audio.transcriptions.create` to the transcription. The STT
|
|
20
|
-
* audio length is only present when the call requests `response_format: 'verbose_json'`.
|
|
21
|
-
*/
|
|
22
|
-
|
|
23
|
-
const { trace, SpanStatusCode } = require('@opentelemetry/api');
|
|
24
|
-
|
|
25
|
-
const TRACER_NAME = 'weflayr.instrumentation.openai_audio';
|
|
26
|
-
const TTS_OPERATION = 'text_to_speech';
|
|
27
|
-
const STT_OPERATION = 'speech_to_text';
|
|
28
|
-
|
|
29
|
-
// Return the gen_ai.system for this call, matching how the OpenAI chat
|
|
30
|
-
// instrumentor attributes the vendor: Azure clients (reached via the same openai
|
|
31
|
-
// SDK) report `azure.ai.openai`, everything else `openai`.
|
|
32
|
-
function _detectSystem(instance) {
|
|
33
|
-
const baseURL = (instance && instance._client && instance._client.baseURL) || '';
|
|
34
|
-
return baseURL.toLowerCase().includes('openai.azure.com') ? 'azure.ai.openai' : 'openai';
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function _startSpan(operation, body, system) {
|
|
38
|
-
const span = trace.getTracer(TRACER_NAME).startSpan(operation);
|
|
39
|
-
span.setAttribute('gen_ai.system', system);
|
|
40
|
-
span.setAttribute('gen_ai.operation.name', operation);
|
|
41
|
-
// `model` lives on the request body passed to both audio entrypoints.
|
|
42
|
-
const model = body && body.model;
|
|
43
|
-
if (model) {
|
|
44
|
-
span.setAttribute('gen_ai.request.model', model);
|
|
45
|
-
}
|
|
46
|
-
return span;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function _setInputCharacters(span, body) {
|
|
50
|
-
// `input` is the text passed to text_to_speech; the billable unit is its length.
|
|
51
|
-
span.setAttribute('gen_ai.usage.input_characters', ((body && body.input) || '').length);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
function _setAudioDuration(span, response) {
|
|
55
|
-
// `duration` (seconds) is only present on a verbose transcription response;
|
|
56
|
-
// it is the billable unit for speech-to-text.
|
|
57
|
-
const durationSecs = response && response.duration;
|
|
58
|
-
if (durationSecs != null) {
|
|
59
|
-
span.setAttribute('gen_ai.usage.input_seconds', durationSecs);
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function _recordError(span, error) {
|
|
64
|
-
span.setStatus({ code: SpanStatusCode.ERROR, message: error?.message ?? String(error) });
|
|
65
|
-
span.recordException(error);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// Wrap a request/response entrypoint: the span spans the single call, and any
|
|
69
|
-
// response-derived billable unit is read off the result.
|
|
70
|
-
function _consumeUnary(span, responsePromise, setResponseAttributes) {
|
|
71
|
-
return responsePromise.then(
|
|
72
|
-
(response) => {
|
|
73
|
-
if (setResponseAttributes) {
|
|
74
|
-
setResponseAttributes(span, response);
|
|
75
|
-
}
|
|
76
|
-
span.end();
|
|
77
|
-
return response;
|
|
78
|
-
},
|
|
79
|
-
(error) => {
|
|
80
|
-
_recordError(span, error);
|
|
81
|
-
span.end();
|
|
82
|
-
throw error;
|
|
83
|
-
}
|
|
84
|
-
);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
class OpenAIAudioInstrumentor {
|
|
88
|
-
constructor() {
|
|
89
|
-
// [ownerPrototype, methodName, originalFunction] for uninstrument().
|
|
90
|
-
this._patched = [];
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
instrument() {
|
|
94
|
-
let Speech;
|
|
95
|
-
let Transcriptions;
|
|
96
|
-
try {
|
|
97
|
-
({ Speech } = require('openai/resources/audio/speech'));
|
|
98
|
-
({ Transcriptions } = require('openai/resources/audio/transcriptions'));
|
|
99
|
-
} catch (err) {
|
|
100
|
-
throw new Error(
|
|
101
|
-
'weflayr.autoInstrument(OPENAI): the openai client is not installed. ' +
|
|
102
|
-
'Install it with: npm install openai\n' +
|
|
103
|
-
`Underlying error: ${err.message}`
|
|
104
|
-
);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// TTS — request body is the 1st arg; billed per character.
|
|
108
|
-
this._wrap(Speech.prototype, {
|
|
109
|
-
operation: TTS_OPERATION,
|
|
110
|
-
setRequestAttributes: _setInputCharacters,
|
|
111
|
-
setResponseAttributes: null,
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
// STT — request body is the 1st arg; billed per second of audio.
|
|
115
|
-
this._wrap(Transcriptions.prototype, {
|
|
116
|
-
operation: STT_OPERATION,
|
|
117
|
-
setRequestAttributes: null,
|
|
118
|
-
setResponseAttributes: _setAudioDuration,
|
|
119
|
-
});
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
uninstrument() {
|
|
123
|
-
for (const [owner, methodName, original] of this._patched) {
|
|
124
|
-
owner[methodName] = original;
|
|
125
|
-
}
|
|
126
|
-
this._patched = [];
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
_wrap(owner, { operation, setRequestAttributes, setResponseAttributes }) {
|
|
130
|
-
const original = owner.create;
|
|
131
|
-
|
|
132
|
-
function wrapper(body, ...rest) {
|
|
133
|
-
const span = _startSpan(operation, body, _detectSystem(this));
|
|
134
|
-
if (setRequestAttributes) {
|
|
135
|
-
setRequestAttributes(span, body);
|
|
136
|
-
}
|
|
137
|
-
return _consumeUnary(
|
|
138
|
-
span,
|
|
139
|
-
Promise.resolve(original.call(this, body, ...rest)),
|
|
140
|
-
setResponseAttributes
|
|
141
|
-
);
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
owner.create = wrapper;
|
|
145
|
-
this._patched.push([owner, 'create', original]);
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
module.exports = { OpenAIAudioInstrumentor };
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Cached-prompt token enrichment complement for the non-streamed OpenAI chat path.
|
|
5
|
-
*
|
|
6
|
-
* `@traceloop/instrumentation-openai` records `gen_ai.usage.{input,output}_tokens`
|
|
7
|
-
* for non-streamed calls but never reads `usage.prompt_tokens_details.cached_tokens`,
|
|
8
|
-
* so spans for prompt-cache hits carry no cached-token count. The Python
|
|
9
|
-
* OpenLLMetry instrumentor sets `gen_ai.usage.cache_read_input_tokens` from that
|
|
10
|
-
* field, so this brings the Node SDK to parity.
|
|
11
|
-
*
|
|
12
|
-
* It wraps the instrumentor instance's `_endSpan({ span, type, result })`, which
|
|
13
|
-
* finalizes the span after the response resolves, letting us stamp the
|
|
14
|
-
* cached-token count off `result.usage` while the span is still open. OpenAI has
|
|
15
|
-
* no separate cache-write count, so only `cache_read_input_tokens` is set.
|
|
16
|
-
*
|
|
17
|
-
* The same hook surfaces the reasoning-token count: reasoning models report it as
|
|
18
|
-
* `usage.completion_tokens_details.reasoning_tokens` (a subset of output_tokens)
|
|
19
|
-
* which the Node instrumentor drops, so this stamps the flat `reasoning_tokens`
|
|
20
|
-
* attribute (the form the Python instrumentor emits and the normalizer reads).
|
|
21
|
-
*/
|
|
22
|
-
|
|
23
|
-
const CACHE_READ_INPUT_TOKENS_ATTR = 'gen_ai.usage.cache_read_input_tokens';
|
|
24
|
-
const REASONING_TOKENS_ATTR = 'gen_ai.usage.reasoning_tokens';
|
|
25
|
-
|
|
26
|
-
class OpenAIPromptCachingInstrumentor {
|
|
27
|
-
constructor() {
|
|
28
|
-
// [instrumentation, methodName, originalFunction] for uninstrument().
|
|
29
|
-
this._patched = [];
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// No-op (and never throws) if the instrumentor's internals have changed shape —
|
|
33
|
-
// the worst case is a missing cached-token count, never a crash.
|
|
34
|
-
instrument(instrumentation) {
|
|
35
|
-
const original = instrumentation && instrumentation._endSpan;
|
|
36
|
-
if (typeof original !== 'function') return;
|
|
37
|
-
|
|
38
|
-
instrumentation._endSpan = function endSpanWithCache(args) {
|
|
39
|
-
try {
|
|
40
|
-
const span = args && args.span;
|
|
41
|
-
const usage = args && args.result && args.result.usage;
|
|
42
|
-
const recording = span && span.isRecording && span.isRecording();
|
|
43
|
-
const cachedTokens =
|
|
44
|
-
usage && usage.prompt_tokens_details && usage.prompt_tokens_details.cached_tokens;
|
|
45
|
-
if (cachedTokens && recording) {
|
|
46
|
-
span.setAttribute(CACHE_READ_INPUT_TOKENS_ATTR, cachedTokens);
|
|
47
|
-
}
|
|
48
|
-
const reasoningTokens =
|
|
49
|
-
usage &&
|
|
50
|
-
usage.completion_tokens_details &&
|
|
51
|
-
usage.completion_tokens_details.reasoning_tokens;
|
|
52
|
-
if (reasoningTokens && recording) {
|
|
53
|
-
span.setAttribute(REASONING_TOKENS_ATTR, reasoningTokens);
|
|
54
|
-
}
|
|
55
|
-
} catch {
|
|
56
|
-
// Never break span finalization over an enrichment failure.
|
|
57
|
-
}
|
|
58
|
-
return original.call(this, args);
|
|
59
|
-
};
|
|
60
|
-
this._patched.push([instrumentation, '_endSpan', original]);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
uninstrument() {
|
|
64
|
-
for (const [owner, methodName, original] of this._patched) {
|
|
65
|
-
owner[methodName] = original;
|
|
66
|
-
}
|
|
67
|
-
this._patched = [];
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
module.exports = { OpenAIPromptCachingInstrumentor };
|
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Hand-written OTel instrumentor for the OpenAI embeddings endpoint.
|
|
5
|
-
*
|
|
6
|
-
* `@traceloop/instrumentation-openai` (the package weflayr uses for OpenAI on
|
|
7
|
-
* Node) does not wrap the embeddings endpoint, so this ships inside weflayr to
|
|
8
|
-
* cover it. The Python SDK gets embeddings for free from OpenLLMetry's
|
|
9
|
-
* instrumentor, so there is no Python counterpart to this file — but the span it
|
|
10
|
-
* emits is shaped to normalize identically. It is registered alongside the
|
|
11
|
-
* traceloop instrumentor whenever `autoInstrument({ aiSdks: 'openai' })` runs.
|
|
12
|
-
*
|
|
13
|
-
* Embeddings are billed per input token, so the span records the prompt tokens
|
|
14
|
-
* read off the response under `gen_ai.usage.input_tokens`; there is no completion.
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
const { trace, SpanStatusCode } = require('@opentelemetry/api');
|
|
18
|
-
|
|
19
|
-
const TRACER_NAME = 'weflayr.instrumentation.openai_embeddings';
|
|
20
|
-
const EMBEDDINGS_OPERATION = 'embeddings';
|
|
21
|
-
|
|
22
|
-
// Return the gen_ai.system for this call, matching how the OpenAI chat
|
|
23
|
-
// instrumentor attributes the vendor: Azure clients (reached via the same openai
|
|
24
|
-
// SDK) report `azure.ai.openai`, everything else `openai`.
|
|
25
|
-
function _detectSystem(instance) {
|
|
26
|
-
const baseURL = (instance && instance._client && instance._client.baseURL) || '';
|
|
27
|
-
return baseURL.toLowerCase().includes('openai.azure.com') ? 'azure.ai.openai' : 'openai';
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function _recordError(span, error) {
|
|
31
|
-
span.setStatus({ code: SpanStatusCode.ERROR, message: error?.message ?? String(error) });
|
|
32
|
-
span.recordException(error);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
class OpenAIEmbeddingsInstrumentor {
|
|
36
|
-
constructor() {
|
|
37
|
-
// [ownerPrototype, methodName, originalFunction] for uninstrument().
|
|
38
|
-
this._patched = [];
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
instrument() {
|
|
42
|
-
let Embeddings;
|
|
43
|
-
try {
|
|
44
|
-
({ Embeddings } = require('openai/resources/embeddings'));
|
|
45
|
-
} catch (err) {
|
|
46
|
-
throw new Error(
|
|
47
|
-
'weflayr.autoInstrument(OPENAI): the openai client is not installed. ' +
|
|
48
|
-
'Install it with: npm install openai\n' +
|
|
49
|
-
`Underlying error: ${err.message}`
|
|
50
|
-
);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const owner = Embeddings.prototype;
|
|
54
|
-
const original = owner.create;
|
|
55
|
-
|
|
56
|
-
function wrapper(body, ...rest) {
|
|
57
|
-
const span = trace.getTracer(TRACER_NAME).startSpan(EMBEDDINGS_OPERATION);
|
|
58
|
-
span.setAttribute('gen_ai.system', _detectSystem(this));
|
|
59
|
-
span.setAttribute('gen_ai.operation.name', EMBEDDINGS_OPERATION);
|
|
60
|
-
if (body && body.model) {
|
|
61
|
-
span.setAttribute('gen_ai.request.model', body.model);
|
|
62
|
-
}
|
|
63
|
-
// One user message per input element, matching the shape the chat
|
|
64
|
-
// instrumentors emit. The span processor strips it when message-content
|
|
65
|
-
// capture is disabled.
|
|
66
|
-
if (body && body.input != null) {
|
|
67
|
-
const inputs = Array.isArray(body.input) ? body.input : [body.input];
|
|
68
|
-
span.setAttribute(
|
|
69
|
-
'gen_ai.input.messages',
|
|
70
|
-
JSON.stringify(
|
|
71
|
-
inputs.map((content) => ({ role: 'user', parts: [{ type: 'text', content }] }))
|
|
72
|
-
)
|
|
73
|
-
);
|
|
74
|
-
}
|
|
75
|
-
return Promise.resolve(original.call(this, body, ...rest)).then(
|
|
76
|
-
(response) => {
|
|
77
|
-
if (response && response.model) {
|
|
78
|
-
span.setAttribute('gen_ai.response.model', response.model);
|
|
79
|
-
}
|
|
80
|
-
const promptTokens = response && response.usage && response.usage.prompt_tokens;
|
|
81
|
-
if (promptTokens != null) {
|
|
82
|
-
span.setAttribute('gen_ai.usage.input_tokens', promptTokens);
|
|
83
|
-
}
|
|
84
|
-
span.end();
|
|
85
|
-
return response;
|
|
86
|
-
},
|
|
87
|
-
(error) => {
|
|
88
|
-
_recordError(span, error);
|
|
89
|
-
span.end();
|
|
90
|
-
throw error;
|
|
91
|
-
}
|
|
92
|
-
);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
owner.create = wrapper;
|
|
96
|
-
this._patched.push([owner, 'create', original]);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
uninstrument() {
|
|
100
|
-
for (const [owner, methodName, original] of this._patched) {
|
|
101
|
-
owner[methodName] = original;
|
|
102
|
-
}
|
|
103
|
-
this._patched = [];
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
module.exports = { OpenAIEmbeddingsInstrumentor };
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Streamed token-usage enrichment complement for the OpenAI chat path.
|
|
5
|
-
*
|
|
6
|
-
* `@traceloop/instrumentation-openai` does not record token usage for streamed
|
|
7
|
-
* calls: its stream handler accumulates `choices[].delta` but never reads the
|
|
8
|
-
* final usage chunk OpenAI sends when the request sets
|
|
9
|
-
* `stream_options: { include_usage: true }`. Non-streaming calls work because
|
|
10
|
-
* OpenAI returns `usage` in the response body, which the instrumentor reads
|
|
11
|
-
* directly. The Python OpenLLMetry instrumentor reads the streamed usage, so
|
|
12
|
-
* this gap is Node-only.
|
|
13
|
-
*
|
|
14
|
-
* It wraps the instrumentor instance's `_streamingWrapPromise`: that handler
|
|
15
|
-
* hands us the span and ends it only after its chunk loop, so when the usage
|
|
16
|
-
* chunk arrives the span is still open and we set the same `gen_ai.usage.*`
|
|
17
|
-
* attributes the instrumentor sets for non-streaming calls.
|
|
18
|
-
*/
|
|
19
|
-
|
|
20
|
-
const USAGE_INPUT_TOKENS_ATTR = 'gen_ai.usage.input_tokens';
|
|
21
|
-
const USAGE_OUTPUT_TOKENS_ATTR = 'gen_ai.usage.output_tokens';
|
|
22
|
-
const USAGE_TOTAL_TOKENS_ATTR = 'gen_ai.usage.total_tokens';
|
|
23
|
-
const CACHE_READ_INPUT_TOKENS_ATTR = 'gen_ai.usage.cache_read_input_tokens';
|
|
24
|
-
|
|
25
|
-
function _setUsageAttributes(span, usage) {
|
|
26
|
-
if (typeof usage.prompt_tokens === 'number') {
|
|
27
|
-
span.setAttribute(USAGE_INPUT_TOKENS_ATTR, usage.prompt_tokens);
|
|
28
|
-
}
|
|
29
|
-
if (typeof usage.completion_tokens === 'number') {
|
|
30
|
-
span.setAttribute(USAGE_OUTPUT_TOKENS_ATTR, usage.completion_tokens);
|
|
31
|
-
}
|
|
32
|
-
if (typeof usage.total_tokens === 'number') {
|
|
33
|
-
span.setAttribute(USAGE_TOTAL_TOKENS_ATTR, usage.total_tokens);
|
|
34
|
-
}
|
|
35
|
-
// Cached-prompt tokens ride in the same streamed usage chunk; mirror the
|
|
36
|
-
// non-streamed cache backfill in get_cache_and_reasoning_telemetry.js.
|
|
37
|
-
const cachedTokens = usage.prompt_tokens_details && usage.prompt_tokens_details.cached_tokens;
|
|
38
|
-
if (cachedTokens) {
|
|
39
|
-
span.setAttribute(CACHE_READ_INPUT_TOKENS_ATTR, cachedTokens);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
class OpenAIStreamingUsageInstrumentor {
|
|
44
|
-
constructor() {
|
|
45
|
-
// [instrumentation, methodName, originalFunction] for uninstrument().
|
|
46
|
-
this._patched = [];
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// No-op if the instrumentor's internals have changed shape — the worst case is
|
|
50
|
-
// streamed spans without token counts, never a crash.
|
|
51
|
-
instrument(instrumentation) {
|
|
52
|
-
const original = instrumentation && instrumentation._streamingWrapPromise;
|
|
53
|
-
if (typeof original !== 'function') return;
|
|
54
|
-
|
|
55
|
-
instrumentation._streamingWrapPromise = function streamingWrapWithUsage(streamArgs) {
|
|
56
|
-
const span = streamArgs && streamArgs.span;
|
|
57
|
-
const wrappedStream = original.call(this, streamArgs);
|
|
58
|
-
if (!span) return wrappedStream;
|
|
59
|
-
|
|
60
|
-
return (async function* backfillUsage() {
|
|
61
|
-
for await (const chunk of wrappedStream) {
|
|
62
|
-
const usage = chunk && chunk.usage;
|
|
63
|
-
if (usage && span.isRecording()) {
|
|
64
|
-
_setUsageAttributes(span, usage);
|
|
65
|
-
}
|
|
66
|
-
yield chunk;
|
|
67
|
-
}
|
|
68
|
-
})();
|
|
69
|
-
};
|
|
70
|
-
this._patched.push([instrumentation, '_streamingWrapPromise', original]);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
uninstrument() {
|
|
74
|
-
for (const [owner, methodName, original] of this._patched) {
|
|
75
|
-
owner[methodName] = original;
|
|
76
|
-
}
|
|
77
|
-
this._patched = [];
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
module.exports = { OpenAIStreamingUsageInstrumentor };
|