@xsai-ext/telemetry 0.4.0-beta.9 → 0.4.1

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/index.d.ts CHANGED
@@ -1,15 +1,25 @@
1
- import { AttributeValue } from '@opentelemetry/api';
2
- import { WithUnknown, GenerateTextOptions, GenerateTextResult, StreamTextOptions, StreamTextResult } from 'xsai';
1
+ import { Attributes } from '@opentelemetry/api';
2
+ import { EmbedOptions, EmbedResult, EmbedManyOptions, EmbedManyResult, WithUnknown, GenerateTextOptions, GenerateTextResult, StreamTextOptions, StreamTextResult } from 'xsai';
3
3
  export * from 'xsai';
4
4
 
5
- type TelemetryMetadata = Record<string, AttributeValue>;
6
5
  interface TelemetryOptions {
7
- metadata?: TelemetryMetadata;
6
+ attributes?: Attributes;
8
7
  }
9
8
  type WithTelemetry<T> = T & {
10
9
  telemetry?: TelemetryOptions;
11
10
  };
12
11
 
12
+ /**
13
+ * @experimental
14
+ * Embeddings with Telemetry.
15
+ */
16
+ declare const embed: (options: WithTelemetry<EmbedOptions>) => Promise<EmbedResult>;
17
+ /**
18
+ * @experimental
19
+ * Embeddings with Telemetry.
20
+ */
21
+ declare const embedMany: (options: WithTelemetry<EmbedManyOptions>) => Promise<EmbedManyResult>;
22
+
13
23
  /**
14
24
  * @experimental
15
25
  * Generating Text with Telemetry.
@@ -22,5 +32,5 @@ declare const generateText: (options: WithUnknown<WithTelemetry<GenerateTextOpti
22
32
  */
23
33
  declare const streamText: (options: WithUnknown<WithTelemetry<StreamTextOptions>>) => StreamTextResult;
24
34
 
25
- export { generateText, streamText };
26
- export type { TelemetryMetadata, TelemetryOptions, WithTelemetry };
35
+ export { embed, embedMany, generateText, streamText };
36
+ export type { TelemetryOptions, WithTelemetry };
package/dist/index.js CHANGED
@@ -1,94 +1,19 @@
1
- import { determineStepType, clean, executeTool, trampoline, chat, responseJSON, DelayedPromise, objCamelToSnake } from 'xsai';
1
+ import { embed as embed$1, clean, embedMany as embedMany$1, trampoline, chat, responseJSON, determineStepType, executeTool, DelayedPromise, objCamelToSnake } from 'xsai';
2
2
  export * from 'xsai';
3
3
  import { trace, SpanStatusCode } from '@opentelemetry/api';
4
4
 
5
- const metadataAttributes = (metadata = {}) => Object.fromEntries(
6
- Object.entries(metadata).map(([key, value]) => [`ai.telemetry.metadata.${key}`, value])
7
- );
8
- const idAttributes = () => {
9
- const id = crypto.randomUUID();
10
- return {
11
- "ai.response.id": id,
12
- "ai.response.timestamp": (/* @__PURE__ */ new Date()).toISOString(),
13
- "gen_ai.response.id": id
14
- };
15
- };
16
- const commonAttributes = (operationId, model) => ({
17
- "ai.model.id": model,
18
- // TODO: provider name
19
- "ai.model.provider": "xsai",
20
- "ai.operationId": operationId,
21
- "ai.response.providerMetadata": "{}",
22
- "operation.name": operationId
23
- });
5
+ var version = "0.4.1";
6
+ var pkg = {
7
+ version: version};
24
8
 
25
- const extractGenerateTextStep = async (options, res) => {
26
- const { choices, usage } = res;
27
- if (!choices?.length)
28
- throw new Error(`No choices returned, response body: ${JSON.stringify(res)}`);
29
- const messages = [];
30
- const toolCalls = [];
31
- const { finish_reason: finishReason, message } = choices[0];
32
- const msgToolCalls = message?.tool_calls ?? [];
33
- const stepType = determineStepType({
34
- finishReason,
35
- maxSteps: options.maxSteps ?? 1,
36
- stepsLength: options.steps?.length ?? 0,
37
- toolCallsLength: msgToolCalls.length
38
- });
39
- messages.push(clean({
40
- ...message,
41
- reasoning_content: void 0
42
- }));
43
- if (finishReason !== "stop" || stepType !== "done") {
44
- for (const toolCall of msgToolCalls) {
45
- toolCalls.push({
46
- args: toolCall.function.arguments,
47
- toolCallId: toolCall.id,
48
- toolCallType: toolCall.type,
49
- toolName: toolCall.function.name
50
- });
51
- }
52
- }
53
- return [
54
- {
55
- finishReason,
56
- stepType,
57
- text: message.content,
58
- toolCalls,
59
- usage
60
- },
61
- {
62
- messages,
63
- msgToolCalls,
64
- reasoningText: message.reasoning_content
65
- }
66
- ];
67
- };
68
- const extractGenerateTextStepPost = async (options, msgToolCalls) => {
69
- const inputMessages = structuredClone(options.messages);
70
- const outputMessages = [];
71
- const toolResults = [];
72
- for (const toolCall of msgToolCalls) {
73
- const { completionToolResult, message } = await executeTool({
74
- abortSignal: options.abortSignal,
75
- messages: inputMessages,
76
- toolCall,
77
- tools: options.tools
78
- });
79
- toolResults.push(completionToolResult);
80
- outputMessages.push(message);
81
- }
82
- return [
83
- toolResults,
84
- outputMessages
85
- ];
86
- };
87
-
88
- const getTracer = () => trace.getTracer("@xsai-ext/telemetry");
9
+ const getTracer = () => trace.getTracer("@xsai-ext/telemetry", pkg.version);
89
10
 
90
11
  const recordErrorOnSpan = (span, error) => {
91
12
  if (error instanceof Error) {
13
+ span.setAttributes({
14
+ "error.message": error.message,
15
+ "error.type": error.name
16
+ });
92
17
  span.recordException({
93
18
  message: error.message,
94
19
  name: error.name,
@@ -122,43 +47,82 @@ const recordSpan = async ({
122
47
  throw error;
123
48
  }
124
49
  });
125
- const recordSpanSync = ({
126
- attributes,
127
- endWhenDone = true,
128
- name,
50
+
51
+ const serverAddressAndPort = (baseURL) => {
52
+ const url = new URL(baseURL);
53
+ return {
54
+ "server.address": url.hostname,
55
+ "server.port": url.port ? Number.parseInt(url.port) : url.protocol === "https:" ? 443 : 80
56
+ };
57
+ };
58
+ const chatSpan = (options, tracer) => ({
59
+ attributes: {
60
+ "gen_ai.input.messages": JSON.stringify(options.messages),
61
+ "gen_ai.operation.name": "chat",
62
+ "gen_ai.provider.name": "openai",
63
+ "gen_ai.request.choice.count": 1,
64
+ "gen_ai.request.frequency_penalty": options.frequencyPenalty,
65
+ "gen_ai.request.model": options.model,
66
+ "gen_ai.request.presence_penalty": options.presencePenalty,
67
+ "gen_ai.request.seed": options.seed,
68
+ "gen_ai.request.stop_sequences": options.stop == null ? void 0 : Array.isArray(options.stop) ? options.stop : [options.stop],
69
+ "gen_ai.request.temperature": options.temperature,
70
+ "gen_ai.request.top_k": options.topK,
71
+ "gen_ai.request.top_p": options.topP,
72
+ "gen_ai.response.id": crypto.randomUUID(),
73
+ "gen_ai.response.model": options.model,
74
+ "gen_ai.tool.definitions": JSON.stringify(options.tools?.map((tool) => ({ function: tool.function, type: tool.type }))),
75
+ ...serverAddressAndPort(options.baseURL),
76
+ ...options.telemetry?.attributes
77
+ // TODO: gen_ai.output.type
78
+ },
79
+ name: `chat ${options.model}`,
80
+ tracer
81
+ });
82
+ const embedSpan = (options, tracer) => ({
83
+ attributes: {
84
+ "gen_ai.embeddings.dimension.count": options.dimensions,
85
+ "gen_ai.operation.name": "embeddings",
86
+ "gen_ai.request.encoding_formats": "float",
87
+ "gen_ai.request.model": options.model,
88
+ ...serverAddressAndPort(options.baseURL),
89
+ ...options.telemetry?.attributes
90
+ },
91
+ name: `embeddings ${options.model}`,
129
92
  tracer
130
- }, fn) => tracer.startActiveSpan(name, { attributes }, (span) => {
131
- try {
132
- const result = fn(span);
133
- if (endWhenDone)
134
- span.end();
135
- return result;
136
- } catch (error) {
137
- try {
138
- recordErrorOnSpan(span, error);
139
- } finally {
140
- span.end();
141
- }
142
- throw error;
143
- }
144
93
  });
145
94
 
146
- const stringifyTool = ({ function: func, type }) => JSON.stringify({ description: func.description, inputSchema: func.parameters, name: func.name, type });
95
+ const embed = async (options) => {
96
+ const tracer = getTracer();
97
+ return recordSpan(embedSpan(options, tracer), async (span) => {
98
+ const result = await embed$1(clean({ ...options, telemetry: void 0 }));
99
+ span.setAttribute("gen_ai.usage.input_tokens", result.usage.prompt_tokens);
100
+ return result;
101
+ });
102
+ };
103
+ const embedMany = async (options) => {
104
+ const tracer = getTracer();
105
+ return recordSpan(embedSpan(options, tracer), async (span) => {
106
+ const result = await embedMany$1(clean({ ...options, telemetry: void 0 }));
107
+ span.setAttribute("gen_ai.usage.input_tokens", result.usage.prompt_tokens);
108
+ return result;
109
+ });
110
+ };
147
111
 
148
112
  const wrapTool = (tool, tracer) => ({
149
113
  execute: async (input, options) => recordSpan({
150
114
  attributes: {
151
- "ai.operationId": "ai.toolCall",
152
- "ai.toolCall.args": JSON.stringify(input),
153
- "ai.toolCall.id": options.toolCallId,
154
- "ai.toolCall.name": tool.function.name,
155
- "operation.name": "ai.toolCall"
115
+ "gen_ai.operation.name": "execute_tool",
116
+ "gen_ai.tool.call.arguments": JSON.stringify(input),
117
+ "gen_ai.tool.call.description": tool.function.description,
118
+ "gen_ai.tool.call.id": options.toolCallId,
119
+ "gen_ai.tool.call.name": tool.function.name
156
120
  },
157
- name: "ai.toolCall",
121
+ name: `execute_tool ${tool.function.name}`,
158
122
  tracer
159
123
  }, async (span) => {
160
124
  const result = await tool.execute(input, options);
161
- span.setAttribute("ai.toolCall.result", JSON.stringify(result));
125
+ span.setAttribute("gen_ai.tool.call.result", JSON.stringify(result));
162
126
  return result;
163
127
  }),
164
128
  function: tool.function,
@@ -167,65 +131,63 @@ const wrapTool = (tool, tracer) => ({
167
131
 
168
132
  const generateText = async (options) => {
169
133
  const tracer = getTracer();
170
- const rawGenerateText = async (options2) => {
134
+ const rawGenerateText = async (options2) => recordSpan(chatSpan(options2, tracer), async (span) => chat({
135
+ ...options2,
136
+ maxSteps: void 0,
137
+ steps: void 0,
138
+ stream: false
139
+ }).then(responseJSON).then(async (res) => {
140
+ const { choices, usage } = res;
141
+ if (!choices?.length)
142
+ throw new Error(`No choices returned, response body: ${JSON.stringify(res)}`);
171
143
  const messages = structuredClone(options2.messages);
172
144
  const steps = options2.steps ? structuredClone(options2.steps) : [];
173
- const [stepWithoutToolCalls, { messages: msgs1, msgToolCalls, reasoningText }] = await recordSpan({
174
- attributes: {
175
- ...idAttributes(),
176
- ...commonAttributes("ai.generateText.doGenerate", options2.model),
177
- ...metadataAttributes(options2.telemetry?.metadata),
178
- ...options2.tools != null && options2.tools.length > 0 ? {
179
- "ai.prompt.toolChoice": JSON.stringify(options2.toolChoice ?? { type: "auto" }),
180
- "ai.prompt.tools": options2.tools.map(stringifyTool)
181
- } : {},
182
- "ai.prompt.messages": JSON.stringify(messages),
183
- "ai.response.model": options2.model,
184
- "gen_ai.request.model": options2.model,
185
- "gen_ai.response.id": crypto.randomUUID(),
186
- "gen_ai.response.model": options2.model,
187
- "gen_ai.system": "xsai"
188
- },
189
- name: "ai.generateText.doGenerate",
190
- tracer
191
- }, async (span) => {
192
- const res = await chat({
193
- ...options2,
194
- maxSteps: void 0,
195
- steps: void 0,
196
- stream: false,
197
- telemetry: void 0
198
- }).then(responseJSON);
199
- const [step2, { messages: msgs, msgToolCalls: msgToolCalls2, reasoningText: reasoningText2 }] = await extractGenerateTextStep({
200
- ...options2,
201
- messages,
202
- steps
203
- }, res);
204
- span.setAttributes({
205
- ...step2.text != null && step2.toolCalls.length === 0 ? { "ai.response.text": step2.text } : {},
206
- ...step2.toolCalls.length > 0 ? { "ai.response.toolCalls": JSON.stringify(step2.toolCalls) } : {},
207
- "ai.response.finishReason": step2.finishReason,
208
- "ai.usage.completionTokens": step2.usage.completion_tokens,
209
- "ai.usage.promptTokens": step2.usage.prompt_tokens,
210
- "gen_ai.response.finish_reasons": [step2.finishReason],
211
- "gen_ai.usage.input_tokens": step2.usage.prompt_tokens,
212
- "gen_ai.usage.output_tokens": step2.usage.completion_tokens
213
- });
214
- return [step2, { messages: msgs, msgToolCalls: msgToolCalls2, reasoningText: reasoningText2 }];
145
+ const toolCalls = [];
146
+ const toolResults = [];
147
+ const { finish_reason: finishReason, message } = choices[0];
148
+ const msgToolCalls = message?.tool_calls ?? [];
149
+ const stepType = determineStepType({
150
+ finishReason,
151
+ maxSteps: options2.maxSteps ?? 1,
152
+ stepsLength: steps.length,
153
+ toolCallsLength: msgToolCalls.length
215
154
  });
216
- const [toolResults, msgs2] = await extractGenerateTextStepPost({
217
- ...options2,
218
- messages}, msgToolCalls);
219
- const step = { ...stepWithoutToolCalls, toolResults };
155
+ messages.push(message);
156
+ span.setAttribute("gen_ai.output.messages", JSON.stringify([message]));
157
+ if (finishReason !== "stop" && stepType !== "done") {
158
+ for (const toolCall of msgToolCalls) {
159
+ const { completionToolCall, completionToolResult, message: message2 } = await executeTool({
160
+ abortSignal: options2.abortSignal,
161
+ messages,
162
+ toolCall,
163
+ tools: options2.tools
164
+ });
165
+ toolCalls.push(completionToolCall);
166
+ toolResults.push(completionToolResult);
167
+ messages.push(message2);
168
+ }
169
+ }
170
+ const step = {
171
+ finishReason,
172
+ stepType,
173
+ text: Array.isArray(message.content) ? message.content.filter((m) => m.type === "text").map((m) => m.text).join("\n") : message.content,
174
+ toolCalls,
175
+ toolResults,
176
+ usage
177
+ };
220
178
  steps.push(step);
221
- messages.push(...msgs1, ...msgs2);
179
+ span.setAttributes({
180
+ "gen_ai.response.finish_reasons": [step.finishReason],
181
+ "gen_ai.usage.input_tokens": step.usage.prompt_tokens,
182
+ "gen_ai.usage.output_tokens": step.usage.completion_tokens
183
+ });
222
184
  if (options2.onStepFinish)
223
185
  await options2.onStepFinish(step);
224
186
  if (step.finishReason === "stop" || step.stepType === "done") {
225
187
  return {
226
188
  finishReason: step.finishReason,
227
189
  messages,
228
- reasoningText,
190
+ reasoningText: message.reasoning_content,
229
191
  steps,
230
192
  text: step.text,
231
193
  toolCalls: step.toolCalls,
@@ -239,33 +201,13 @@ const generateText = async (options) => {
239
201
  steps
240
202
  });
241
203
  }
242
- };
243
- return recordSpan({
244
- attributes: {
245
- ...commonAttributes("ai.generateText", options.model),
246
- ...metadataAttributes(options.telemetry?.metadata),
247
- "ai.prompt": JSON.stringify({ messages: options.messages })
248
- },
249
- name: "ai.generateText",
250
- tracer
251
- }, async (span) => {
252
- const result = await trampoline(async () => rawGenerateText({
253
- ...options,
254
- tools: options.tools?.map((tool) => wrapTool(tool, tracer))
255
- }));
256
- span.setAttributes({
257
- ...result.toolCalls.length > 0 ? { "ai.response.toolCalls": JSON.stringify(result.toolCalls) } : {},
258
- ...result.text != null ? { "ai.response.text": result.text } : {},
259
- "ai.response.finishReason": result.finishReason,
260
- "ai.usage.completionTokens": result.usage.completion_tokens,
261
- "ai.usage.promptTokens": result.usage.prompt_tokens
262
- });
263
- return result;
264
- });
204
+ }));
205
+ return trampoline(async () => rawGenerateText({
206
+ ...options,
207
+ tools: options.tools?.map((tool) => wrapTool(tool, tracer))
208
+ }));
265
209
  };
266
210
 
267
- const now = () => globalThis?.performance?.now() ?? Date.now();
268
-
269
211
  const parseChunk = (text) => {
270
212
  if (!text || !text.startsWith("data:"))
271
213
  return [void 0, false];
@@ -312,14 +254,17 @@ const streamText = (options) => {
312
254
  const maxSteps = options.maxSteps ?? 1;
313
255
  let usage;
314
256
  let totalUsage;
257
+ let reasoningField;
315
258
  const resultSteps = new DelayedPromise();
316
259
  const resultMessages = new DelayedPromise();
317
260
  const resultUsage = new DelayedPromise();
318
261
  const resultTotalUsage = new DelayedPromise();
319
262
  let eventCtrl;
320
263
  let textCtrl;
264
+ let reasoningTextCtrl;
321
265
  const eventStream = new ReadableStream({ start: (controller) => eventCtrl = controller });
322
266
  const textStream = new ReadableStream({ start: (controller) => textCtrl = controller });
267
+ const reasoningTextStream = new ReadableStream({ start: (controller) => reasoningTextCtrl = controller });
323
268
  const pushEvent = (stepEvent) => {
324
269
  eventCtrl?.enqueue(stepEvent);
325
270
  void options.onEvent?.(stepEvent);
@@ -329,26 +274,7 @@ const streamText = (options) => {
329
274
  void options.onStepFinish?.(step);
330
275
  };
331
276
  const tools = options.tools != null && options.tools.length > 0 ? options.tools.map((tool) => wrapTool(tool, tracer)) : void 0;
332
- const doStream = async () => recordSpan({
333
- attributes: {
334
- ...idAttributes(),
335
- ...commonAttributes("ai.streamText.doStream", options.model),
336
- ...metadataAttributes(options.telemetry?.metadata),
337
- ...tools != null && tools.length > 0 && {
338
- "ai.prompt.toolChoice": JSON.stringify(options.toolChoice ?? { type: "auto" }),
339
- "ai.prompt.tools": tools.map(stringifyTool)
340
- },
341
- "ai.prompt.messages": JSON.stringify(options.messages),
342
- "ai.response.model": options.model,
343
- "gen_ai.request.model": options.model,
344
- "gen_ai.response.id": crypto.randomUUID(),
345
- "gen_ai.response.model": options.model,
346
- "gen_ai.system": "xsai"
347
- },
348
- name: "ai.streamText.doStream",
349
- tracer
350
- }, async (span) => {
351
- const startMs = now();
277
+ const doStream = async () => recordSpan(chatSpan({ ...options, messages }, tracer), async (span) => {
352
278
  const { body: stream } = await chat({
353
279
  ...options,
354
280
  maxSteps: void 0,
@@ -363,18 +289,24 @@ const streamText = (options) => {
363
289
  completion_tokens: totalUsage.completion_tokens + u.completion_tokens,
364
290
  prompt_tokens: totalUsage.prompt_tokens + u.prompt_tokens,
365
291
  total_tokens: totalUsage.total_tokens + u.total_tokens
366
- } : { ...u };
292
+ } : u;
367
293
  };
368
294
  let text = "";
295
+ let reasoningText;
369
296
  const pushText = (content) => {
370
297
  textCtrl?.enqueue(content);
371
298
  text += content;
372
299
  };
300
+ const pushReasoningText = (reasoningContent) => {
301
+ if (reasoningText == null)
302
+ reasoningText = "";
303
+ reasoningTextCtrl?.enqueue(reasoningContent);
304
+ reasoningText += reasoningContent;
305
+ };
373
306
  const tool_calls = [];
374
307
  const toolCalls = [];
375
308
  const toolResults = [];
376
309
  let finishReason = "other";
377
- let firstChunk = true;
378
310
  await stream.pipeThrough(transformChunk()).pipeTo(new WritableStream({
379
311
  abort: (reason) => {
380
312
  eventCtrl?.error(reason);
@@ -384,23 +316,22 @@ const streamText = (options) => {
384
316
  },
385
317
  // eslint-disable-next-line sonarjs/cognitive-complexity
386
318
  write: (chunk) => {
387
- if (firstChunk) {
388
- const msToFirstChunk = now() - startMs;
389
- span.addEvent("ai.stream.firstChunk", {
390
- "ai.response.msToFirstChunk": msToFirstChunk
391
- });
392
- span.setAttributes({
393
- "ai.response.msToFirstChunk": msToFirstChunk
394
- });
395
- firstChunk = false;
396
- }
397
319
  if (chunk.usage)
398
320
  pushUsage(chunk.usage);
399
321
  if (chunk.choices == null || chunk.choices.length === 0)
400
322
  return;
401
323
  const choice = chunk.choices[0];
402
- if (choice.delta.reasoning_content != null)
324
+ if (choice.delta.reasoning != null) {
325
+ if (reasoningField !== "reasoning")
326
+ reasoningField = "reasoning";
327
+ pushEvent({ text: choice.delta.reasoning, type: "reasoning-delta" });
328
+ pushReasoningText(choice.delta.reasoning);
329
+ } else if (choice.delta.reasoning_content != null) {
330
+ if (reasoningField !== "reasoning_content")
331
+ reasoningField = "reasoning_content";
403
332
  pushEvent({ text: choice.delta.reasoning_content, type: "reasoning-delta" });
333
+ pushReasoningText(choice.delta.reasoning_content);
334
+ }
404
335
  if (choice.finish_reason != null)
405
336
  finishReason = choice.finish_reason;
406
337
  if (choice.delta.tool_calls?.length === 0 || choice.delta.tool_calls == null) {
@@ -420,24 +351,40 @@ const streamText = (options) => {
420
351
  ...toolCall,
421
352
  function: {
422
353
  ...toolCall.function,
423
- arguments: toolCall.function.arguments
354
+ arguments: toolCall.function.arguments ?? ""
424
355
  }
425
356
  };
426
- pushEvent({ toolCallId: toolCall.id, toolName: toolCall.function.name, type: "tool-call-streaming-start" });
357
+ pushEvent({
358
+ toolCallId: toolCall.id,
359
+ toolName: toolCall.function.name,
360
+ type: "tool-call-streaming-start"
361
+ });
427
362
  } else {
428
363
  tool_calls[index].function.arguments += toolCall.function.arguments;
429
- pushEvent({ argsTextDelta: toolCall.function.arguments, toolCallId: toolCall.id, toolName: toolCall.function.name ?? tool_calls[index].function.name, type: "tool-call-delta" });
364
+ pushEvent({
365
+ argsTextDelta: toolCall.function.arguments,
366
+ toolCallId: toolCall.id,
367
+ toolName: toolCall.function.name ?? tool_calls[index].function.name,
368
+ type: "tool-call-delta"
369
+ });
430
370
  }
431
371
  }
432
372
  }
433
373
  }
434
374
  }));
435
- messages.push({ content: text, role: "assistant", tool_calls });
375
+ const message = {
376
+ ...reasoningField != null ? { [reasoningField]: reasoningText } : {},
377
+ content: text,
378
+ role: "assistant",
379
+ tool_calls: tool_calls.length > 0 ? tool_calls : void 0
380
+ };
381
+ messages.push(message);
382
+ span.setAttribute("gen_ai.output.messages", JSON.stringify([message]));
436
383
  if (tool_calls.length !== 0) {
437
384
  for (const toolCall of tool_calls) {
438
385
  if (toolCall == null)
439
386
  continue;
440
- const { completionToolCall, completionToolResult, message } = await executeTool({
387
+ const { completionToolCall, completionToolResult, message: message2 } = await executeTool({
441
388
  abortSignal: options.abortSignal,
442
389
  messages,
443
390
  toolCall,
@@ -445,7 +392,7 @@ const streamText = (options) => {
445
392
  });
446
393
  toolCalls.push(completionToolCall);
447
394
  toolResults.push(completionToolResult);
448
- messages.push(message);
395
+ messages.push(message2);
449
396
  pushEvent({ ...completionToolCall, type: "tool-call" });
450
397
  pushEvent({ ...completionToolResult, type: "tool-result" });
451
398
  }
@@ -465,19 +412,9 @@ const streamText = (options) => {
465
412
  usage
466
413
  };
467
414
  pushStep(step);
468
- const msToFinish = now() - startMs;
469
- span.addEvent("ai.stream.finish");
470
415
  span.setAttributes({
471
- "ai.response.msToFinish": msToFinish,
472
- ...step.toolCalls.length > 0 && { "ai.response.toolCalls": JSON.stringify(step.toolCalls) },
473
- "ai.response.finishReason": step.finishReason,
474
- "ai.response.text": step.text != null ? step.text : "",
475
416
  "gen_ai.response.finish_reasons": [step.finishReason],
476
417
  ...step.usage && {
477
- "ai.response.avgOutputTokensPerSecond": 1e3 * (step.usage.completion_tokens ?? 0) / msToFinish,
478
- "ai.usage.inputTokens": step.usage.prompt_tokens,
479
- "ai.usage.outputTokens": step.usage.completion_tokens,
480
- "ai.usage.totalTokens": step.usage.total_tokens,
481
418
  "gen_ai.usage.input_tokens": step.usage.prompt_tokens,
482
419
  "gen_ai.usage.output_tokens": step.usage.completion_tokens
483
420
  }
@@ -485,61 +422,38 @@ const streamText = (options) => {
485
422
  if (toolCalls.length !== 0 && steps.length < maxSteps)
486
423
  return async () => doStream();
487
424
  });
488
- return recordSpanSync({
489
- attributes: {
490
- ...commonAttributes("ai.streamText", options.model),
491
- ...metadataAttributes(options.telemetry?.metadata),
492
- "ai.prompt": JSON.stringify({ messages: options.messages })
493
- },
494
- endWhenDone: false,
495
- name: "ai.streamText",
496
- tracer
497
- }, (rootSpan) => {
498
- void (async () => {
499
- try {
500
- await trampoline(async () => doStream());
501
- eventCtrl?.close();
502
- textCtrl?.close();
503
- } catch (err) {
504
- eventCtrl?.error(err);
505
- textCtrl?.error(err);
506
- resultSteps.reject(err);
507
- resultMessages.reject(err);
508
- resultUsage.reject(err);
509
- resultTotalUsage.reject(err);
510
- } finally {
511
- resultSteps.resolve(steps);
512
- resultMessages.resolve(messages);
513
- resultUsage.resolve(usage);
514
- resultTotalUsage.resolve(totalUsage);
515
- const finishStep = steps.at(-1);
516
- if (finishStep) {
517
- rootSpan.setAttributes({
518
- ...finishStep.toolCalls.length > 0 && { "ai.response.toolCalls": JSON.stringify(finishStep.toolCalls) },
519
- "ai.response.finishReason": finishStep.finishReason,
520
- "ai.response.text": finishStep.text != null ? finishStep.text : ""
521
- });
522
- }
523
- if (totalUsage) {
524
- rootSpan.setAttributes({
525
- "ai.usage.inputTokens": totalUsage.prompt_tokens,
526
- "ai.usage.outputTokens": totalUsage.completion_tokens,
527
- "ai.usage.totalTokens": totalUsage.total_tokens
528
- });
529
- }
530
- void options.onFinish?.(finishStep);
531
- rootSpan.end();
532
- }
533
- })();
534
- return {
535
- fullStream: eventStream,
536
- messages: resultMessages.promise,
537
- steps: resultSteps.promise,
538
- textStream,
539
- totalUsage: resultTotalUsage.promise,
540
- usage: resultUsage.promise
541
- };
542
- });
425
+ void (async () => {
426
+ try {
427
+ await trampoline(async () => doStream());
428
+ eventCtrl?.close();
429
+ textCtrl?.close();
430
+ reasoningTextCtrl?.close();
431
+ } catch (err) {
432
+ eventCtrl?.error(err);
433
+ textCtrl?.error(err);
434
+ reasoningTextCtrl?.error(err);
435
+ resultSteps.reject(err);
436
+ resultMessages.reject(err);
437
+ resultUsage.reject(err);
438
+ resultTotalUsage.reject(err);
439
+ } finally {
440
+ resultSteps.resolve(steps);
441
+ resultMessages.resolve(messages);
442
+ resultUsage.resolve(usage);
443
+ resultTotalUsage.resolve(totalUsage);
444
+ const finishStep = steps.at(-1);
445
+ void options.onFinish?.(finishStep);
446
+ }
447
+ })();
448
+ return {
449
+ fullStream: eventStream,
450
+ messages: resultMessages.promise,
451
+ reasoningTextStream,
452
+ steps: resultSteps.promise,
453
+ textStream,
454
+ totalUsage: resultTotalUsage.promise,
455
+ usage: resultUsage.promise
456
+ };
543
457
  };
544
458
 
545
- export { generateText, streamText };
459
+ export { embed, embedMany, generateText, streamText };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@xsai-ext/telemetry",
3
3
  "type": "module",
4
- "version": "0.4.0-beta.9",
4
+ "version": "0.4.1",
5
5
  "description": "extra-small AI SDK.",
6
6
  "author": "Moeru AI",
7
7
  "license": "MIT",
@@ -30,18 +30,17 @@
30
30
  ],
31
31
  "dependencies": {
32
32
  "@opentelemetry/api": "^1.9.0",
33
- "xsai": "~0.4.0-beta.9"
33
+ "xsai": "~0.4.1"
34
34
  },
35
35
  "devDependencies": {
36
- "@opentelemetry/sdk-trace-base": "^2.0.1",
37
- "@opentelemetry/sdk-trace-node": "^2.0.1",
38
- "ai": "^5.0.28",
39
- "ollama-ai-provider-v2": "^1.2.1",
40
- "zod": "^4.1.5"
36
+ "@langfuse/otel": "^4.5.1",
37
+ "@opentelemetry/sdk-trace-base": "^2.2.0",
38
+ "@opentelemetry/sdk-trace-node": "^2.2.0",
39
+ "zod": "^4.2.1"
41
40
  },
42
41
  "scripts": {
43
42
  "build": "pkgroll",
44
- "test": "vitest run --update"
43
+ "test": "vitest run"
45
44
  },
46
45
  "main": "./dist/index.js",
47
46
  "types": "./dist/index.d.ts"