weflayr 0.26.1 → 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 +8 -5
- 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_input_message_content_telemetry.js +33 -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 +8 -5
- 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
|
@@ -7,8 +7,10 @@
|
|
|
7
7
|
* breakpoints (`cache_control` / `cachePoint` blocks it never records), the
|
|
8
8
|
* streamed output message content (its stream path formats an empty-parts
|
|
9
9
|
* message), the request's tool definitions (it never reads the body's
|
|
10
|
-
* `toolConfig`)
|
|
11
|
-
* Anthropic's block shape, so Nova's `toolUse` blocks come through raw)
|
|
10
|
+
* `toolConfig`), the tool-call output parts (its block mapper only knows
|
|
11
|
+
* Anthropic's block shape, so Nova's `toolUse` blocks come through raw), and
|
|
12
|
+
* everything about a model whose body is OpenAI's chat completions format (it
|
|
13
|
+
* reads only the vendors it knows).
|
|
12
14
|
*/
|
|
13
15
|
export declare class BedrockTelemetryInstrumentor {
|
|
14
16
|
private _subInstrumentors;
|
|
@@ -8,8 +8,10 @@
|
|
|
8
8
|
* breakpoints (`cache_control` / `cachePoint` blocks it never records), the
|
|
9
9
|
* streamed output message content (its stream path formats an empty-parts
|
|
10
10
|
* message), the request's tool definitions (it never reads the body's
|
|
11
|
-
* `toolConfig`)
|
|
12
|
-
* Anthropic's block shape, so Nova's `toolUse` blocks come through raw)
|
|
11
|
+
* `toolConfig`), the tool-call output parts (its block mapper only knows
|
|
12
|
+
* Anthropic's block shape, so Nova's `toolUse` blocks come through raw), and
|
|
13
|
+
* everything about a model whose body is OpenAI's chat completions format (it
|
|
14
|
+
* reads only the vendors it knows).
|
|
13
15
|
*/
|
|
14
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
17
|
exports.BedrockTelemetryInstrumentor = void 0;
|
|
@@ -18,6 +20,7 @@ const get_cache_telemetry_1 = require("./get_cache_telemetry");
|
|
|
18
20
|
const get_input_message_content_telemetry_1 = require("./get_input_message_content_telemetry");
|
|
19
21
|
const get_streaming_output_message_telemetry_1 = require("./get_streaming_output_message_telemetry");
|
|
20
22
|
const get_model_id_telemetry_1 = require("./get_model_id_telemetry");
|
|
23
|
+
const get_openai_chat_format_telemetry_1 = require("./get_openai_chat_format_telemetry");
|
|
21
24
|
const get_reasoning_output_telemetry_1 = require("./get_reasoning_output_telemetry");
|
|
22
25
|
const get_tool_call_output_telemetry_1 = require("./get_tool_call_output_telemetry");
|
|
23
26
|
const get_tool_definitions_telemetry_1 = require("./get_tool_definitions_telemetry");
|
|
@@ -33,14 +36,18 @@ class BedrockTelemetryInstrumentor {
|
|
|
33
36
|
new get_tool_call_output_telemetry_1.BedrockToolCallOutputInstrumentor(),
|
|
34
37
|
new get_model_id_telemetry_1.BedrockModelIdInstrumentor(),
|
|
35
38
|
new get_reasoning_output_telemetry_1.BedrockReasoningOutputInstrumentor(),
|
|
39
|
+
new get_openai_chat_format_telemetry_1.BedrockOpenaiChatFormatInstrumentor(),
|
|
36
40
|
];
|
|
37
41
|
}
|
|
38
42
|
instrument(instrumentation) {
|
|
39
43
|
for (const sub of this._subInstrumentors)
|
|
40
44
|
sub.instrument(instrumentation);
|
|
41
45
|
}
|
|
46
|
+
// Reversed: several complements wrap the same instrumentor method
|
|
47
|
+
// (`_startSpan`), so the patches have to be unwound last-patched-first or an
|
|
48
|
+
// earlier wrapper is written back over a later one and stays installed.
|
|
42
49
|
uninstrument() {
|
|
43
|
-
for (const sub of this._subInstrumentors)
|
|
50
|
+
for (const sub of [...this._subInstrumentors].reverse())
|
|
44
51
|
sub.uninstrument();
|
|
45
52
|
}
|
|
46
53
|
}
|
|
@@ -19,6 +19,7 @@ const TRACER_NAME = 'weflayr.instrumentation.cohere_embeddings';
|
|
|
19
19
|
// Matches the gen_ai.system the cohere chat instrumentors set (Python + Node).
|
|
20
20
|
const SYSTEM = 'Cohere';
|
|
21
21
|
const EMBEDDINGS_OPERATION = 'embeddings';
|
|
22
|
+
const EMBEDDINGS_INPUT_ATTR = 'weflayr.embeddings.input';
|
|
22
23
|
function _recordError(span, error) {
|
|
23
24
|
span.setStatus({ code: api_1.SpanStatusCode.ERROR, message: error?.message ?? String(error) });
|
|
24
25
|
span.recordException(error);
|
|
@@ -32,13 +33,10 @@ function _wrapEmbed(owner, patched) {
|
|
|
32
33
|
if (request && request.model) {
|
|
33
34
|
span.setAttribute('gen_ai.request.model', request.model);
|
|
34
35
|
}
|
|
35
|
-
// The embedded texts
|
|
36
|
-
//
|
|
36
|
+
// The embedded texts, as passed; the worker shapes them. The span
|
|
37
|
+
// processor strips the attribute when content capture is off.
|
|
37
38
|
if (request && Array.isArray(request.texts) && request.texts.length) {
|
|
38
|
-
span.setAttribute(
|
|
39
|
-
role: 'user',
|
|
40
|
-
parts: [{ type: 'text', content: text }],
|
|
41
|
-
}))));
|
|
39
|
+
span.setAttribute(EMBEDDINGS_INPUT_ATTR, JSON.stringify(request.texts));
|
|
42
40
|
}
|
|
43
41
|
return Promise.resolve(original.call(this, request, ...rest)).then((response) => {
|
|
44
42
|
// Node SDK reports the billed input tokens under camelCase keys.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Input message content for Cohere v2 chat spans.
|
|
3
|
+
*
|
|
4
|
+
* Mirror of the Python complement's message shaping: a v2 request's messages in
|
|
5
|
+
* the standard role+parts shape, with media blocks reduced to a byte-less stub
|
|
6
|
+
* so the payload stays off the span while the analyses reading these messages
|
|
7
|
+
* still see that the call carried content the stored text does not.
|
|
8
|
+
*
|
|
9
|
+
* Python wraps the OpenLLMetry instrumentor to stamp this (that instrumentor
|
|
10
|
+
* covers v2 but records its messages in a shape OTel rejects). The Node
|
|
11
|
+
* traceloop instrumentor covers only the v1 client, so on this side the shaping
|
|
12
|
+
* is called by weflayr's own v2 chat instrumentor
|
|
13
|
+
* (`get_v2_chat_telemetry.ts`) instead.
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* The v2 request's messages in the role+parts shape.
|
|
17
|
+
*
|
|
18
|
+
* e.g. `messages=[{role: "user", content: [{type: "text", text: "hi"},
|
|
19
|
+
* {type: "image_url", image_url: {url: "data:image/png;base64,AAA"}}]}]`
|
|
20
|
+
* -> `[{role: "user", parts: [{type: "text", content: "hi"},
|
|
21
|
+
* {type: "blob", mime_type: "image/png"}]}]`.
|
|
22
|
+
*/
|
|
23
|
+
export declare function extractInputMessages(request: unknown): Record<string, unknown>[];
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Input message content for Cohere v2 chat spans.
|
|
4
|
+
*
|
|
5
|
+
* Mirror of the Python complement's message shaping: a v2 request's messages in
|
|
6
|
+
* the standard role+parts shape, with media blocks reduced to a byte-less stub
|
|
7
|
+
* so the payload stays off the span while the analyses reading these messages
|
|
8
|
+
* still see that the call carried content the stored text does not.
|
|
9
|
+
*
|
|
10
|
+
* Python wraps the OpenLLMetry instrumentor to stamp this (that instrumentor
|
|
11
|
+
* covers v2 but records its messages in a shape OTel rejects). The Node
|
|
12
|
+
* traceloop instrumentor covers only the v1 client, so on this side the shaping
|
|
13
|
+
* is called by weflayr's own v2 chat instrumentor
|
|
14
|
+
* (`get_v2_chat_telemetry.ts`) instead.
|
|
15
|
+
*/
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.extractInputMessages = extractInputMessages;
|
|
18
|
+
const get_data_uri_mime_type_1 = require("../../get-data-uri-mime-type");
|
|
19
|
+
function asObject(value) {
|
|
20
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
21
|
+
? value
|
|
22
|
+
: null;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* An image block as a part: an inline data URI keeps its mime type only, a
|
|
26
|
+
* hosted image keeps its url (which is not a payload).
|
|
27
|
+
*/
|
|
28
|
+
function getImagePart(block) {
|
|
29
|
+
// The JS client spells the key imageUrl; the wire (and the Python client)
|
|
30
|
+
// spell it image_url.
|
|
31
|
+
const rawImageUrl = block.imageUrl ?? block.image_url;
|
|
32
|
+
const imageUrl = asObject(rawImageUrl);
|
|
33
|
+
const url = String((imageUrl ? imageUrl.url : rawImageUrl) ?? '');
|
|
34
|
+
const mimeType = (0, get_data_uri_mime_type_1.getDataUriMimeType)(url);
|
|
35
|
+
if (mimeType === null)
|
|
36
|
+
return { type: 'uri', uri: url };
|
|
37
|
+
return { type: 'blob', mime_type: mimeType };
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* A v2 message's `content` as normalized parts. A plain string is one text
|
|
41
|
+
* part; a list keeps its text, turns images into a byte-less stub, and keeps
|
|
42
|
+
* any other block under its own type - naming it is what lets the analyses tell
|
|
43
|
+
* a text-only prompt from one whose content was not stored.
|
|
44
|
+
*/
|
|
45
|
+
function contentToParts(content) {
|
|
46
|
+
if (typeof content === 'string')
|
|
47
|
+
return [{ type: 'text', content }];
|
|
48
|
+
const parts = [];
|
|
49
|
+
for (const rawBlock of Array.isArray(content) ? content : []) {
|
|
50
|
+
const block = asObject(rawBlock);
|
|
51
|
+
if (block === null)
|
|
52
|
+
continue;
|
|
53
|
+
if (block.type === 'text') {
|
|
54
|
+
parts.push({ type: 'text', content: String(block.text ?? '') });
|
|
55
|
+
}
|
|
56
|
+
else if (block.type === 'image_url') {
|
|
57
|
+
parts.push(getImagePart(block));
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
parts.push({ type: String(block.type ?? 'unknown') });
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return parts;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* An assistant message's `tool_calls` as `tool_call` parts, so an agentic v2
|
|
67
|
+
* conversation replays with the exchange it was built on.
|
|
68
|
+
*/
|
|
69
|
+
function toolCallsToParts(toolCalls) {
|
|
70
|
+
const parts = [];
|
|
71
|
+
for (const rawToolCall of Array.isArray(toolCalls) ? toolCalls : []) {
|
|
72
|
+
const toolCall = asObject(rawToolCall);
|
|
73
|
+
if (toolCall === null)
|
|
74
|
+
continue;
|
|
75
|
+
const functionCall = asObject(toolCall.function) ?? {};
|
|
76
|
+
parts.push({
|
|
77
|
+
type: 'tool_call',
|
|
78
|
+
id: String(toolCall.id ?? ''),
|
|
79
|
+
name: String(functionCall.name ?? ''),
|
|
80
|
+
arguments: functionCall.arguments ?? {},
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
return parts;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* The v2 request's messages in the role+parts shape.
|
|
87
|
+
*
|
|
88
|
+
* e.g. `messages=[{role: "user", content: [{type: "text", text: "hi"},
|
|
89
|
+
* {type: "image_url", image_url: {url: "data:image/png;base64,AAA"}}]}]`
|
|
90
|
+
* -> `[{role: "user", parts: [{type: "text", content: "hi"},
|
|
91
|
+
* {type: "blob", mime_type: "image/png"}]}]`.
|
|
92
|
+
*/
|
|
93
|
+
function extractInputMessages(request) {
|
|
94
|
+
const messages = asObject(request)?.messages;
|
|
95
|
+
const inputMessages = [];
|
|
96
|
+
for (const rawMessage of Array.isArray(messages) ? messages : []) {
|
|
97
|
+
const message = asObject(rawMessage);
|
|
98
|
+
if (message === null)
|
|
99
|
+
continue;
|
|
100
|
+
const parts = contentToParts(message.content);
|
|
101
|
+
parts.push(...toolCallsToParts(message.tool_calls ?? message.toolCalls));
|
|
102
|
+
inputMessages.push({ role: String(message.role ?? ''), parts });
|
|
103
|
+
}
|
|
104
|
+
return inputMessages;
|
|
105
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hand-written OTel instrumentor for the Cohere v2 chat endpoint.
|
|
3
|
+
*
|
|
4
|
+
* `@traceloop/instrumentation-cohere` wraps `CohereClient.prototype` only, so a
|
|
5
|
+
* call made through the v2 client (`CohereClientV2`, the only client that
|
|
6
|
+
* accepts images) produces no span at all on Node. OpenLLMetry's Python cohere
|
|
7
|
+
* instrumentor does cover v2, so on that side weflayr only corrects the
|
|
8
|
+
* messages it records; here weflayr owns the whole span. Both end up producing
|
|
9
|
+
* the same normalized row.
|
|
10
|
+
*
|
|
11
|
+
* Only the non-streaming `chat` is covered: `chatStream` has no span on Node
|
|
12
|
+
* yet.
|
|
13
|
+
*/
|
|
14
|
+
export declare class CohereV2ChatInstrumentor {
|
|
15
|
+
private _patched;
|
|
16
|
+
instrument(): void;
|
|
17
|
+
uninstrument(): void;
|
|
18
|
+
}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Hand-written OTel instrumentor for the Cohere v2 chat endpoint.
|
|
4
|
+
*
|
|
5
|
+
* `@traceloop/instrumentation-cohere` wraps `CohereClient.prototype` only, so a
|
|
6
|
+
* call made through the v2 client (`CohereClientV2`, the only client that
|
|
7
|
+
* accepts images) produces no span at all on Node. OpenLLMetry's Python cohere
|
|
8
|
+
* instrumentor does cover v2, so on that side weflayr only corrects the
|
|
9
|
+
* messages it records; here weflayr owns the whole span. Both end up producing
|
|
10
|
+
* the same normalized row.
|
|
11
|
+
*
|
|
12
|
+
* Only the non-streaming `chat` is covered: `chatStream` has no span on Node
|
|
13
|
+
* yet.
|
|
14
|
+
*/
|
|
15
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
+
exports.CohereV2ChatInstrumentor = void 0;
|
|
17
|
+
const api_1 = require("@opentelemetry/api");
|
|
18
|
+
const get_input_message_content_telemetry_1 = require("./get_input_message_content_telemetry");
|
|
19
|
+
const logger_1 = require("../../logger");
|
|
20
|
+
const TRACER_NAME = 'weflayr.instrumentation.cohere_v2_chat';
|
|
21
|
+
// Marks the patched `chat` so a second autoInstrument('cohere') - a new
|
|
22
|
+
// instrumentor instance, and possibly a second copy of this module - does not
|
|
23
|
+
// wrap the wrapper and emit two spans per call.
|
|
24
|
+
const PATCHED_MARKER = Symbol.for('weflayr.cohere.v2.chat.patched');
|
|
25
|
+
// Matches the gen_ai.system the v1 chat instrumentors set (Python + Node).
|
|
26
|
+
const SYSTEM = 'Cohere';
|
|
27
|
+
const CHAT_OPERATION = 'chat';
|
|
28
|
+
function asObject(value) {
|
|
29
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
30
|
+
? value
|
|
31
|
+
: null;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* The response as one assistant message: its content blocks as text parts, the
|
|
35
|
+
* plan it writes before calling a tool as a reasoning part, and its tool calls
|
|
36
|
+
* as `tool_call` parts. Mirrors the Python complement's `build_output_messages`.
|
|
37
|
+
*/
|
|
38
|
+
function buildOutputMessages(response) {
|
|
39
|
+
const message = asObject(asObject(response)?.message);
|
|
40
|
+
if (message === null)
|
|
41
|
+
return [];
|
|
42
|
+
const parts = [];
|
|
43
|
+
if (message.toolPlan) {
|
|
44
|
+
parts.push({ type: 'reasoning', content: String(message.toolPlan) });
|
|
45
|
+
}
|
|
46
|
+
for (const rawBlock of Array.isArray(message.content) ? message.content : []) {
|
|
47
|
+
const block = asObject(rawBlock);
|
|
48
|
+
if (block?.text)
|
|
49
|
+
parts.push({ type: 'text', content: String(block.text) });
|
|
50
|
+
}
|
|
51
|
+
for (const rawToolCall of Array.isArray(message.toolCalls) ? message.toolCalls : []) {
|
|
52
|
+
const toolCall = asObject(rawToolCall);
|
|
53
|
+
const functionCall = asObject(toolCall?.function);
|
|
54
|
+
if (!functionCall?.name)
|
|
55
|
+
continue;
|
|
56
|
+
// v2 follows the OpenAI shape: the arguments come as a JSON string.
|
|
57
|
+
let parsedArguments = functionCall.arguments;
|
|
58
|
+
if (typeof parsedArguments === 'string') {
|
|
59
|
+
try {
|
|
60
|
+
parsedArguments = JSON.parse(parsedArguments);
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
// A string the model did not close as valid JSON is kept as is.
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
const part = {
|
|
67
|
+
type: 'tool_call',
|
|
68
|
+
name: String(functionCall.name),
|
|
69
|
+
arguments: parsedArguments ?? {},
|
|
70
|
+
};
|
|
71
|
+
if (toolCall?.id)
|
|
72
|
+
part.id = String(toolCall.id);
|
|
73
|
+
parts.push(part);
|
|
74
|
+
}
|
|
75
|
+
if (!parts.length)
|
|
76
|
+
return [];
|
|
77
|
+
const outputMessage = { role: 'assistant', parts };
|
|
78
|
+
const finishReason = asObject(response)?.finishReason;
|
|
79
|
+
if (finishReason)
|
|
80
|
+
outputMessage.finish_reason = String(finishReason);
|
|
81
|
+
return [outputMessage];
|
|
82
|
+
}
|
|
83
|
+
function setRequestAttributes(span, request) {
|
|
84
|
+
const chatRequest = asObject(request);
|
|
85
|
+
if (chatRequest === null)
|
|
86
|
+
return;
|
|
87
|
+
if (chatRequest.model)
|
|
88
|
+
span.setAttribute('gen_ai.request.model', String(chatRequest.model));
|
|
89
|
+
// No sampling parameters: OpenLLMetry records none for Cohere either, and a
|
|
90
|
+
// half-populated input_params would differ between the two SDKs.
|
|
91
|
+
const inputMessages = (0, get_input_message_content_telemetry_1.extractInputMessages)(chatRequest);
|
|
92
|
+
if (inputMessages.length) {
|
|
93
|
+
span.setAttribute('gen_ai.input.messages', JSON.stringify(inputMessages));
|
|
94
|
+
}
|
|
95
|
+
if (Array.isArray(chatRequest.tools) && chatRequest.tools.length) {
|
|
96
|
+
span.setAttribute('gen_ai.tool.definitions', JSON.stringify(chatRequest.tools));
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
function setResponseAttributes(span, response) {
|
|
100
|
+
const chatResponse = asObject(response);
|
|
101
|
+
if (chatResponse === null)
|
|
102
|
+
return;
|
|
103
|
+
if (chatResponse.id)
|
|
104
|
+
span.setAttribute('gen_ai.response.id', String(chatResponse.id));
|
|
105
|
+
if (chatResponse.finishReason) {
|
|
106
|
+
span.setAttribute('gen_ai.response.finish_reasons', [String(chatResponse.finishReason)]);
|
|
107
|
+
}
|
|
108
|
+
const outputMessages = buildOutputMessages(chatResponse);
|
|
109
|
+
if (outputMessages.length) {
|
|
110
|
+
span.setAttribute('gen_ai.output.messages', JSON.stringify(outputMessages));
|
|
111
|
+
}
|
|
112
|
+
// Cohere reports what it bills under billedUnits; tokens holds the raw counts.
|
|
113
|
+
const usage = asObject(chatResponse.usage);
|
|
114
|
+
const billedUnits = asObject(usage?.billedUnits) ?? asObject(usage?.tokens);
|
|
115
|
+
if (typeof billedUnits?.inputTokens === 'number') {
|
|
116
|
+
span.setAttribute('gen_ai.usage.input_tokens', billedUnits.inputTokens);
|
|
117
|
+
}
|
|
118
|
+
if (typeof billedUnits?.outputTokens === 'number') {
|
|
119
|
+
span.setAttribute('gen_ai.usage.output_tokens', billedUnits.outputTokens);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
class CohereV2ChatInstrumentor {
|
|
123
|
+
// [ownerPrototype, methodName, originalFunction] for uninstrument().
|
|
124
|
+
_patched = [];
|
|
125
|
+
instrument() {
|
|
126
|
+
let V2Client;
|
|
127
|
+
try {
|
|
128
|
+
// CohereClientV2 binds `chat` as an instance property in its constructor,
|
|
129
|
+
// so the underlying V2Client class is what has to be patched - and it has
|
|
130
|
+
// to be patched before the caller builds their client.
|
|
131
|
+
({ V2Client } = require('cohere-ai/api/resources/v2/client/Client'));
|
|
132
|
+
}
|
|
133
|
+
catch (err) {
|
|
134
|
+
// A private path inside cohere-ai, so a release can move it. v2 calls
|
|
135
|
+
// then go uninstrumented, which is what they were before this file: the
|
|
136
|
+
// v1 client the traceloop instrumentor covers must keep working.
|
|
137
|
+
(0, logger_1.debug)('cohere: v2 chat not instrumented (%s)', err.message);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
const original = V2Client.prototype.chat;
|
|
141
|
+
// Not a function means the release moved or renamed the method: leave v2
|
|
142
|
+
// uninstrumented rather than install a wrapper whose original.call() would
|
|
143
|
+
// throw on the caller's first chat.
|
|
144
|
+
if (typeof original !== 'function' || original[PATCHED_MARKER])
|
|
145
|
+
return;
|
|
146
|
+
function wrapper(request, ...rest) {
|
|
147
|
+
const span = api_1.trace.getTracer(TRACER_NAME).startSpan(CHAT_OPERATION);
|
|
148
|
+
span.setAttribute('gen_ai.system', SYSTEM);
|
|
149
|
+
span.setAttribute('gen_ai.operation.name', CHAT_OPERATION);
|
|
150
|
+
try {
|
|
151
|
+
setRequestAttributes(span, request);
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
// Never break the user's Cohere call over an enrichment failure.
|
|
155
|
+
}
|
|
156
|
+
return Promise.resolve(original.call(this, request, ...rest)).then((response) => {
|
|
157
|
+
try {
|
|
158
|
+
setResponseAttributes(span, response);
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
// As above: the answer still reaches the caller.
|
|
162
|
+
}
|
|
163
|
+
span.end();
|
|
164
|
+
return response;
|
|
165
|
+
}, (error) => {
|
|
166
|
+
span.setStatus({
|
|
167
|
+
code: api_1.SpanStatusCode.ERROR,
|
|
168
|
+
message: error?.message ?? String(error),
|
|
169
|
+
});
|
|
170
|
+
span.recordException(error);
|
|
171
|
+
span.end();
|
|
172
|
+
throw error;
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
wrapper[PATCHED_MARKER] = true;
|
|
176
|
+
V2Client.prototype.chat = wrapper;
|
|
177
|
+
this._patched.push([V2Client.prototype, 'chat', original]);
|
|
178
|
+
}
|
|
179
|
+
uninstrument() {
|
|
180
|
+
for (const [owner, methodName, original] of this._patched) {
|
|
181
|
+
owner[methodName] = original;
|
|
182
|
+
}
|
|
183
|
+
this._patched = [];
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
exports.CohereV2ChatInstrumentor = CohereV2ChatInstrumentor;
|
|
@@ -7,7 +7,9 @@
|
|
|
7
7
|
* - the embeddings endpoint it doesn't cover,
|
|
8
8
|
* - the request's structured-output schema, and
|
|
9
9
|
* - function calling: it records neither the request's tools nor the
|
|
10
|
-
* response's tool calls
|
|
10
|
+
* response's tool calls, and
|
|
11
|
+
* - the v2 chat endpoint, which it does not wrap at all (so a v2 call, the
|
|
12
|
+
* only way to send Cohere an image, produced no span).
|
|
11
13
|
*/
|
|
12
14
|
export declare class CohereTelemetryInstrumentor {
|
|
13
15
|
private _subInstrumentors;
|
|
@@ -8,7 +8,9 @@
|
|
|
8
8
|
* - the embeddings endpoint it doesn't cover,
|
|
9
9
|
* - the request's structured-output schema, and
|
|
10
10
|
* - function calling: it records neither the request's tools nor the
|
|
11
|
-
* response's tool calls
|
|
11
|
+
* response's tool calls, and
|
|
12
|
+
* - the v2 chat endpoint, which it does not wrap at all (so a v2 call, the
|
|
13
|
+
* only way to send Cohere an image, produced no span).
|
|
12
14
|
*/
|
|
13
15
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
16
|
exports.CohereTelemetryInstrumentor = void 0;
|
|
@@ -18,6 +20,7 @@ const get_embeddings_telemetry_1 = require("./get_embeddings_telemetry");
|
|
|
18
20
|
const get_structured_output_schema_telemetry_2 = require("./get_structured_output_schema_telemetry");
|
|
19
21
|
const get_tool_call_output_telemetry_1 = require("./get_tool_call_output_telemetry");
|
|
20
22
|
const get_tool_definitions_telemetry_1 = require("./get_tool_definitions_telemetry");
|
|
23
|
+
const get_v2_chat_telemetry_1 = require("./get_v2_chat_telemetry");
|
|
21
24
|
class CohereTelemetryInstrumentor {
|
|
22
25
|
_subInstrumentors;
|
|
23
26
|
constructor() {
|
|
@@ -27,6 +30,7 @@ class CohereTelemetryInstrumentor {
|
|
|
27
30
|
new get_structured_output_schema_telemetry_1.TraceloopStructuredOutputSchemaInstrumentor(get_structured_output_schema_telemetry_2.readStructuredOutputSchema),
|
|
28
31
|
new get_tool_definitions_telemetry_1.CohereToolDefinitionsInstrumentor(),
|
|
29
32
|
new get_tool_call_output_telemetry_1.CohereToolCallOutputInstrumentor(),
|
|
33
|
+
new get_v2_chat_telemetry_1.CohereV2ChatInstrumentor(),
|
|
30
34
|
];
|
|
31
35
|
}
|
|
32
36
|
instrument(instrumentation) {
|
|
@@ -4,9 +4,13 @@
|
|
|
4
4
|
* `@traceloop/instrumentation-google-generativeai` wraps generate_content but not
|
|
5
5
|
* embeddings, so this covers it by starting its own span on the private
|
|
6
6
|
* `Models.prototype.embedContentInternal` (which the public `embedContent`
|
|
7
|
-
* delegates to).
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
7
|
+
* delegates to).
|
|
8
|
+
*
|
|
9
|
+
* Both surfaces bill embeddings per input token, and only Vertex reports them:
|
|
10
|
+
* it returns `tokenCount` in each embedding's `statistics`, which the span
|
|
11
|
+
* records as `gen_ai.usage.input_tokens`. The Gemini Developer API returns no
|
|
12
|
+
* usage at all, on any API version, so its calls carry no token attribute and
|
|
13
|
+
* weflayr cannot price them, which `_warnDeveloperApiUnpriced` says once per
|
|
14
|
+
* process.
|
|
11
15
|
*/
|
|
12
16
|
export declare function instrumentEmbeddings(Models: any, patched: [any, string, any][]): void;
|
|
@@ -5,10 +5,14 @@
|
|
|
5
5
|
* `@traceloop/instrumentation-google-generativeai` wraps generate_content but not
|
|
6
6
|
* embeddings, so this covers it by starting its own span on the private
|
|
7
7
|
* `Models.prototype.embedContentInternal` (which the public `embedContent`
|
|
8
|
-
* delegates to).
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
8
|
+
* delegates to).
|
|
9
|
+
*
|
|
10
|
+
* Both surfaces bill embeddings per input token, and only Vertex reports them:
|
|
11
|
+
* it returns `tokenCount` in each embedding's `statistics`, which the span
|
|
12
|
+
* records as `gen_ai.usage.input_tokens`. The Gemini Developer API returns no
|
|
13
|
+
* usage at all, on any API version, so its calls carry no token attribute and
|
|
14
|
+
* weflayr cannot price them, which `_warnDeveloperApiUnpriced` says once per
|
|
15
|
+
* process.
|
|
12
16
|
*/
|
|
13
17
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
18
|
exports.instrumentEmbeddings = instrumentEmbeddings;
|
|
@@ -16,34 +20,59 @@ const api_1 = require("@opentelemetry/api");
|
|
|
16
20
|
const get_provider_name_1 = require("./get_provider_name");
|
|
17
21
|
const TRACER_NAME = 'weflayr.instrumentation.google.embeddings';
|
|
18
22
|
const EMBEDDINGS_OPERATION = 'embeddings';
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
23
|
+
const EMBEDDINGS_INPUT_ATTR = 'weflayr.embeddings.input';
|
|
24
|
+
const GEMINI_DEVELOPER_API_SYSTEM = 'gemini';
|
|
25
|
+
// The Gemini Developer API reports no embeddings usage, so those calls are
|
|
26
|
+
// stored with a cost of zero; say so once rather than let that pass silently.
|
|
27
|
+
let _developerApiWarningSaid = false;
|
|
28
|
+
// One embedded content is a string, a Content/Part object, or an array of
|
|
29
|
+
// those (a Content's parts); its text is the concatenation across them.
|
|
30
|
+
function _contentText(content) {
|
|
31
|
+
if (content == null)
|
|
32
|
+
return '';
|
|
33
|
+
if (typeof content === 'string')
|
|
34
|
+
return content;
|
|
35
|
+
if (Array.isArray(content))
|
|
36
|
+
return content.map(_contentText).join('');
|
|
37
|
+
if (typeof content.text === 'string')
|
|
38
|
+
return content.text;
|
|
39
|
+
return _contentText(content.parts);
|
|
40
|
+
}
|
|
41
|
+
// The text of each embedded content, e.g. ["ab", "cde"] for
|
|
42
|
+
// contents: ["ab", { parts: [{ text: "cde" }] }].
|
|
43
|
+
function _embeddedTexts(contents) {
|
|
22
44
|
if (contents == null)
|
|
23
|
-
return
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
if (Array.isArray(contents)) {
|
|
27
|
-
return contents.reduce((total, item) => total + _contentCharacters(item), 0);
|
|
28
|
-
}
|
|
29
|
-
if (typeof contents === 'object') {
|
|
30
|
-
if (typeof contents.text === 'string')
|
|
31
|
-
return contents.text.length;
|
|
32
|
-
if (Array.isArray(contents.parts))
|
|
33
|
-
return _contentCharacters(contents.parts);
|
|
34
|
-
}
|
|
35
|
-
return 0;
|
|
45
|
+
return [];
|
|
46
|
+
const contentsList = Array.isArray(contents) ? contents : [contents];
|
|
47
|
+
return contentsList.map(_contentText);
|
|
36
48
|
}
|
|
37
|
-
//
|
|
38
|
-
//
|
|
39
|
-
function
|
|
40
|
-
if (
|
|
41
|
-
return
|
|
49
|
+
// Says once per process that Gemini Developer API embeddings calls cannot be
|
|
50
|
+
// priced.
|
|
51
|
+
function _warnDeveloperApiUnpriced() {
|
|
52
|
+
if (_developerApiWarningSaid)
|
|
53
|
+
return;
|
|
54
|
+
_developerApiWarningSaid = true;
|
|
55
|
+
console.warn('weflayr: Gemini Developer API embeddings calls cannot be priced: the API ' +
|
|
56
|
+
'returns no token usage for them, so they are recorded with a cost of ' +
|
|
57
|
+
'zero. Use Vertex, which reports the token count itself.');
|
|
58
|
+
}
|
|
59
|
+
// Records the input tokens Vertex reports per embedding, e.g. 7 for a call
|
|
60
|
+
// embedding two texts of 2 and 5 tokens. The Gemini Developer API reports none,
|
|
61
|
+
// and then the span carries no token attribute.
|
|
62
|
+
function _recordInputTokens(span, response) {
|
|
63
|
+
try {
|
|
64
|
+
const reported = ((response && response.embeddings) || []).map((embedding) => embedding && embedding.statistics && embedding.statistics.tokenCount);
|
|
65
|
+
// A total is only worth recording when every embedding reports a number:
|
|
66
|
+
// one missing or unreadable count would make the sum too small.
|
|
67
|
+
if (reported.length && reported.every((tokenCount) => Number.isFinite(tokenCount))) {
|
|
68
|
+
span.setAttribute('gen_ai.usage.input_tokens', reported.reduce((total, tokenCount) => total + tokenCount, 0));
|
|
69
|
+
}
|
|
42
70
|
}
|
|
43
|
-
|
|
44
|
-
|
|
71
|
+
catch (error) {
|
|
72
|
+
// Telemetry must never break the app's call, and a swallowed failure still
|
|
73
|
+
// has to be findable.
|
|
74
|
+
console.debug('weflayr: reading Gemini embeddings usage failed', error);
|
|
45
75
|
}
|
|
46
|
-
return { role: 'user', parts: [] };
|
|
47
76
|
}
|
|
48
77
|
function _recordError(span, error) {
|
|
49
78
|
span.setStatus({ code: api_1.SpanStatusCode.ERROR, message: error?.message ?? String(error) });
|
|
@@ -58,20 +87,24 @@ function instrumentEmbeddings(Models, patched) {
|
|
|
58
87
|
return;
|
|
59
88
|
function wrapper(params, ...rest) {
|
|
60
89
|
const span = api_1.trace.getTracer(TRACER_NAME).startSpan(EMBEDDINGS_OPERATION);
|
|
61
|
-
|
|
90
|
+
const system = (0, get_provider_name_1.getProviderName)(this);
|
|
91
|
+
span.setAttribute('gen_ai.system', system);
|
|
62
92
|
span.setAttribute('gen_ai.operation.name', EMBEDDINGS_OPERATION);
|
|
63
93
|
if (params && params.model) {
|
|
64
94
|
// The chat path strips the leading "models/" prefix; mirror it.
|
|
65
95
|
span.setAttribute('gen_ai.request.model', params.model.replace(/^models\//, ''));
|
|
66
96
|
}
|
|
67
|
-
|
|
68
|
-
// The embedded
|
|
69
|
-
//
|
|
70
|
-
if (
|
|
71
|
-
|
|
72
|
-
|
|
97
|
+
const embeddedTexts = _embeddedTexts(params && params.contents);
|
|
98
|
+
// The embedded texts; the worker shapes them. The span processor strips
|
|
99
|
+
// the attribute when message-content capture is disabled.
|
|
100
|
+
if (embeddedTexts.length) {
|
|
101
|
+
span.setAttribute(EMBEDDINGS_INPUT_ATTR, JSON.stringify(embeddedTexts));
|
|
102
|
+
}
|
|
103
|
+
if (system === GEMINI_DEVELOPER_API_SYSTEM) {
|
|
104
|
+
_warnDeveloperApiUnpriced();
|
|
73
105
|
}
|
|
74
106
|
return Promise.resolve(original.call(this, params, ...rest)).then((response) => {
|
|
107
|
+
_recordInputTokens(span, response);
|
|
75
108
|
span.end();
|
|
76
109
|
return response;
|
|
77
110
|
}, (error) => {
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* Developer API. We read the client's Vertex flag and return the value to set as
|
|
7
7
|
* `gen_ai.system` (which the normalizer prefers): `vertex_ai` / `gemini`,
|
|
8
8
|
* matching what the Python google-genai instrumentor reports natively. The
|
|
9
|
-
* worker normalizes `
|
|
9
|
+
* worker normalizes `gemini` to the canonical `google` and `vertex_ai` to
|
|
10
|
+
* `vertex`. Node-only.
|
|
10
11
|
*/
|
|
11
12
|
export declare function getProviderName(instance: any): string;
|
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
* Developer API. We read the client's Vertex flag and return the value to set as
|
|
8
8
|
* `gen_ai.system` (which the normalizer prefers): `vertex_ai` / `gemini`,
|
|
9
9
|
* matching what the Python google-genai instrumentor reports natively. The
|
|
10
|
-
* worker normalizes `
|
|
10
|
+
* worker normalizes `gemini` to the canonical `google` and `vertex_ai` to
|
|
11
|
+
* `vertex`. Node-only.
|
|
11
12
|
*/
|
|
12
13
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
14
|
exports.getProviderName = getProviderName;
|