@xsai-ext/telemetry 0.5.0-beta.1 → 0.5.0-beta.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +53 -99
  2. package/package.json +8 -8
package/dist/index.js CHANGED
@@ -1,9 +1,9 @@
1
- import { embed as embed$1, clean, embedMany as embedMany$1, trampoline, resolveStepOptions, chat, responseJSON, InvalidResponseError, stepCountAtLeast, executeTool, shouldStop, determineStepType, RemoteAPIError, JSONParseError, DelayedPromise, objCamelToSnake } from 'xsai';
1
+ import { embed as embed$1, clean, embedMany as embedMany$1, trampoline, resolvePrepareStep, chat, responseJSON, normalizeChatCompletionUsage, InvalidResponseError, stepCountAtLeast, executeTool, shouldStop, DelayedPromise, objCamelToSnake, computeTotalUsage } from 'xsai';
2
2
  export * from 'xsai';
3
3
  import { trace, SpanStatusCode } from '@opentelemetry/api';
4
- import { EventSourceParserStream } from 'eventsource-parser/stream';
4
+ import { createControlledStream, errorControllers, closeControllers, EventSourceParserStream, JsonMessageTransformStream } from '@xsai/shared-stream';
5
5
 
6
- var version = "0.5.0-beta.1";
6
+ var version = "0.5.0-beta.3";
7
7
  var pkg = {
8
8
  version: version};
9
9
 
@@ -135,8 +135,8 @@ const generateText = async (options) => {
135
135
  const rawGenerateText = async (options2) => {
136
136
  const messages = options2.steps == null ? structuredClone(options2.messages) : options2.messages;
137
137
  const steps = options2.steps ?? [];
138
- const stepOptions = await resolveStepOptions({
139
- messages,
138
+ const stepOptions = await resolvePrepareStep({
139
+ input: messages,
140
140
  model: options2.model,
141
141
  prepareStep: options2.prepareStep,
142
142
  stepNumber: steps.length,
@@ -145,20 +145,21 @@ const generateText = async (options) => {
145
145
  });
146
146
  return recordSpan(chatSpan({
147
147
  ...options2,
148
- messages: stepOptions.messages,
148
+ messages: stepOptions.input,
149
149
  model: stepOptions.model,
150
150
  toolChoice: stepOptions.toolChoice
151
151
  }, tracer), async (span) => chat({
152
152
  ...options2,
153
153
  maxSteps: void 0,
154
- messages: stepOptions.messages,
154
+ messages: stepOptions.input,
155
155
  model: stepOptions.model,
156
156
  steps: void 0,
157
157
  stopWhen: void 0,
158
158
  stream: false,
159
159
  toolChoice: stepOptions.toolChoice
160
160
  }).then(responseJSON).then(async (res) => {
161
- const { choices, usage } = res;
161
+ const { choices } = res;
162
+ const usage = normalizeChatCompletionUsage(res.usage);
162
163
  if (!choices?.length) {
163
164
  const responseBody = JSON.stringify(res);
164
165
  throw new InvalidResponseError(`No choices returned, response body: ${responseBody}`, {
@@ -182,13 +183,17 @@ const generateText = async (options) => {
182
183
  tools: options2.tools
183
184
  }))
184
185
  );
185
- for (const { completionToolCall, completionToolResult, message: message2 } of results) {
186
+ for (const { completionToolCall, completionToolResult, result } of results) {
186
187
  toolCalls.push(completionToolCall);
187
188
  toolResults.push(completionToolResult);
188
- messages.push(message2);
189
+ messages.push({
190
+ content: result,
191
+ role: "tool",
192
+ tool_call_id: completionToolCall.toolCallId
193
+ });
189
194
  }
190
195
  }
191
- const stopStep = {
196
+ const step = {
192
197
  finishReason,
193
198
  text: Array.isArray(message.content) ? message.content.filter((m) => m.type === "text").map((m) => m.text).join("\n") : message.content,
194
199
  toolCalls,
@@ -196,25 +201,16 @@ const generateText = async (options) => {
196
201
  usage
197
202
  };
198
203
  const stop = shouldStop(stopWhen, {
199
- messages,
200
- step: stopStep,
201
- steps: [...steps, stopStep]
204
+ input: messages,
205
+ step,
206
+ steps: [...steps, step]
202
207
  });
203
208
  const willContinue = toolCalls.length > 0 && !stop;
204
- const step = {
205
- ...stopStep,
206
- stepType: determineStepType({
207
- finishReason,
208
- stepsLength: steps.length,
209
- toolCallsLength: toolCalls.length,
210
- willContinue
211
- })
212
- };
213
209
  steps.push(step);
214
210
  span.setAttributes({
215
211
  "gen_ai.response.finish_reasons": [step.finishReason],
216
- "gen_ai.usage.input_tokens": step.usage.prompt_tokens,
217
- "gen_ai.usage.output_tokens": step.usage.completion_tokens
212
+ "gen_ai.usage.input_tokens": step.usage.inputTokens,
213
+ "gen_ai.usage.output_tokens": step.usage.outputTokens
218
214
  });
219
215
  if (options2.onStepFinish)
220
216
  await options2.onStepFinish(step);
@@ -244,31 +240,6 @@ const generateText = async (options) => {
244
240
  }));
245
241
  };
246
242
 
247
- const parseChunk = (data) => {
248
- if (data.startsWith("{") && data.includes('"error":')) {
249
- throw new RemoteAPIError(`Error from server: ${data}`, {
250
- responseBody: data
251
- });
252
- }
253
- try {
254
- return JSON.parse(data);
255
- } catch (cause) {
256
- throw new JSONParseError(`Failed to parse stream chunk JSON: ${data}`, {
257
- cause,
258
- text: data
259
- });
260
- }
261
- };
262
- const transformChunk = () => {
263
- return new TransformStream({
264
- transform: async (chunk, controller) => {
265
- if (!chunk.data || chunk.data === "[DONE]")
266
- return;
267
- controller.enqueue(parseChunk(chunk.data));
268
- }
269
- });
270
- };
271
-
272
243
  const streamText = (options) => {
273
244
  const tracer = getTracer();
274
245
  const steps = [];
@@ -281,14 +252,11 @@ const streamText = (options) => {
281
252
  const resultMessages = new DelayedPromise();
282
253
  const resultUsage = new DelayedPromise();
283
254
  const resultTotalUsage = new DelayedPromise();
284
- let eventCtrl;
285
- let textCtrl;
286
- let reasoningTextCtrl;
287
- const eventStream = new ReadableStream({ start: (controller) => eventCtrl = controller });
288
- const textStream = new ReadableStream({ start: (controller) => textCtrl = controller });
289
- const reasoningTextStream = new ReadableStream({ start: (controller) => reasoningTextCtrl = controller });
255
+ const [eventStream, eventCtrl] = createControlledStream();
256
+ const [textStream, textCtrl] = createControlledStream();
257
+ const [reasoningTextStream, reasoningTextCtrl] = createControlledStream();
290
258
  const pushEvent = (stepEvent) => {
291
- eventCtrl?.enqueue(stepEvent);
259
+ eventCtrl.current?.enqueue(stepEvent);
292
260
  void options.onEvent?.(stepEvent);
293
261
  };
294
262
  const pushStep = (step) => {
@@ -297,8 +265,8 @@ const streamText = (options) => {
297
265
  };
298
266
  const tools = options.tools != null && options.tools.length > 0 ? options.tools.map((tool) => wrapTool(tool, tracer)) : void 0;
299
267
  const doStream = async () => {
300
- const stepOptions = await resolveStepOptions({
301
- messages,
268
+ const stepOptions = await resolvePrepareStep({
269
+ input: messages,
302
270
  model: options.model,
303
271
  prepareStep: options.prepareStep,
304
272
  stepNumber: steps.length,
@@ -307,14 +275,14 @@ const streamText = (options) => {
307
275
  });
308
276
  return recordSpan(chatSpan({
309
277
  ...options,
310
- messages: stepOptions.messages,
278
+ messages: stepOptions.input,
311
279
  model: stepOptions.model,
312
280
  toolChoice: stepOptions.toolChoice
313
281
  }, tracer), async (span) => {
314
282
  const { body: stream } = await chat({
315
283
  ...options,
316
284
  maxSteps: void 0,
317
- messages: stepOptions.messages,
285
+ messages: stepOptions.input,
318
286
  model: stepOptions.model,
319
287
  stopWhen: void 0,
320
288
  stream: true,
@@ -323,40 +291,35 @@ const streamText = (options) => {
323
291
  tools
324
292
  });
325
293
  const pushUsage = (u) => {
326
- usage = u;
327
- totalUsage = totalUsage ? {
328
- completion_tokens: totalUsage.completion_tokens + u.completion_tokens,
329
- prompt_tokens: totalUsage.prompt_tokens + u.prompt_tokens,
330
- total_tokens: totalUsage.total_tokens + u.total_tokens
331
- } : u;
294
+ if (u == null)
295
+ return;
296
+ usage = normalizeChatCompletionUsage(u);
297
+ totalUsage = computeTotalUsage(totalUsage, usage);
332
298
  };
333
299
  let text = "";
334
300
  let reasoningText;
335
301
  const pushText = (content) => {
336
- textCtrl?.enqueue(content);
302
+ textCtrl.current?.enqueue(content);
337
303
  text += content;
338
304
  };
339
305
  const pushReasoningText = (reasoningContent) => {
340
- if (reasoningText == null)
341
- reasoningText = "";
342
- reasoningTextCtrl?.enqueue(reasoningContent);
306
+ reasoningText ??= "";
307
+ reasoningTextCtrl.current?.enqueue(reasoningContent);
343
308
  reasoningText += reasoningContent;
344
309
  };
345
310
  const tool_calls = [];
346
311
  const toolCalls = [];
347
312
  const toolResults = [];
348
313
  let finishReason = "other";
349
- await stream.pipeThrough(new TextDecoderStream()).pipeThrough(new EventSourceParserStream()).pipeThrough(transformChunk()).pipeTo(new WritableStream({
314
+ await stream.pipeThrough(new TextDecoderStream()).pipeThrough(new EventSourceParserStream()).pipeThrough(new JsonMessageTransformStream()).pipeTo(new WritableStream({
350
315
  abort: (reason) => {
351
- eventCtrl?.error(reason);
352
- textCtrl?.error(reason);
316
+ errorControllers(reason, eventCtrl, textCtrl, reasoningTextCtrl);
353
317
  },
354
318
  close: () => {
355
319
  },
356
320
  // eslint-disable-next-line sonarjs/cognitive-complexity
357
321
  write: (chunk) => {
358
- if (chunk.usage)
359
- pushUsage(chunk.usage);
322
+ pushUsage(chunk.usage);
360
323
  if (chunk.choices == null || chunk.choices.length === 0)
361
324
  return;
362
325
  const choice = chunk.choices[0];
@@ -429,10 +392,14 @@ const streamText = (options) => {
429
392
  tools
430
393
  }))
431
394
  );
432
- for (const { completionToolCall, completionToolResult, message: message2 } of results) {
395
+ for (const { completionToolCall, completionToolResult, result } of results) {
433
396
  toolCalls.push(completionToolCall);
434
397
  toolResults.push(completionToolResult);
435
- messages.push(message2);
398
+ messages.push({
399
+ content: result,
400
+ role: "tool",
401
+ tool_call_id: completionToolCall.toolCallId
402
+ });
436
403
  pushEvent({ ...completionToolCall, type: "tool-call" });
437
404
  pushEvent({ ...completionToolResult, type: "tool-result" });
438
405
  }
@@ -443,7 +410,7 @@ const streamText = (options) => {
443
410
  usage
444
411
  });
445
412
  }
446
- const stopStep = {
413
+ const step = {
447
414
  finishReason,
448
415
  text,
449
416
  toolCalls,
@@ -451,26 +418,17 @@ const streamText = (options) => {
451
418
  usage
452
419
  };
453
420
  const stop = shouldStop(stopWhen, {
454
- messages,
455
- step: stopStep,
456
- steps: [...steps, stopStep]
421
+ input: messages,
422
+ step,
423
+ steps: [...steps, step]
457
424
  });
458
425
  const willContinue = toolCalls.length > 0 && !stop;
459
- const step = {
460
- ...stopStep,
461
- stepType: determineStepType({
462
- finishReason,
463
- stepsLength: steps.length,
464
- toolCallsLength: toolCalls.length,
465
- willContinue
466
- })
467
- };
468
426
  pushStep(step);
469
427
  span.setAttributes({
470
428
  "gen_ai.response.finish_reasons": [step.finishReason],
471
429
  ...step.usage && {
472
- "gen_ai.usage.input_tokens": step.usage.prompt_tokens,
473
- "gen_ai.usage.output_tokens": step.usage.completion_tokens
430
+ "gen_ai.usage.input_tokens": step.usage.inputTokens,
431
+ "gen_ai.usage.output_tokens": step.usage.outputTokens
474
432
  }
475
433
  });
476
434
  if (willContinue)
@@ -490,18 +448,14 @@ const streamText = (options) => {
490
448
  finalError ??= err;
491
449
  }
492
450
  if (finalError != null) {
493
- eventCtrl?.error(finalError);
494
- textCtrl?.error(finalError);
495
- reasoningTextCtrl?.error(finalError);
451
+ errorControllers(finalError, eventCtrl, textCtrl, reasoningTextCtrl);
496
452
  resultSteps.reject(finalError);
497
453
  resultMessages.reject(finalError);
498
454
  resultUsage.reject(finalError);
499
455
  resultTotalUsage.reject(finalError);
500
456
  return;
501
457
  }
502
- eventCtrl?.close();
503
- textCtrl?.close();
504
- reasoningTextCtrl?.close();
458
+ closeControllers(eventCtrl, textCtrl, reasoningTextCtrl);
505
459
  resultSteps.resolve(steps);
506
460
  resultMessages.resolve(messages);
507
461
  resultUsage.resolve(usage);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@xsai-ext/telemetry",
3
3
  "type": "module",
4
- "version": "0.5.0-beta.1",
4
+ "version": "0.5.0-beta.3",
5
5
  "description": "extra-small AI SDK.",
6
6
  "author": "Moeru AI",
7
7
  "license": "MIT",
@@ -29,15 +29,15 @@
29
29
  "dist"
30
30
  ],
31
31
  "dependencies": {
32
- "@opentelemetry/api": "^1.9.0",
33
- "eventsource-parser": "^3.0.6",
34
- "xsai": "~0.5.0-beta.1"
32
+ "@opentelemetry/api": "^1.9.1",
33
+ "xsai": "0.5.0-beta.3",
34
+ "@xsai/shared-stream": "0.5.0-beta.3"
35
35
  },
36
36
  "devDependencies": {
37
- "@langfuse/otel": "^4.5.1",
38
- "@opentelemetry/sdk-trace-base": "^2.2.0",
39
- "@opentelemetry/sdk-trace-node": "^2.2.0",
40
- "zod": "^4.2.1"
37
+ "@langfuse/otel": "^5.1.0",
38
+ "@opentelemetry/sdk-trace-base": "^2.6.1",
39
+ "@opentelemetry/sdk-trace-node": "^2.6.1",
40
+ "zod": "^4.3.6"
41
41
  },
42
42
  "scripts": {
43
43
  "build": "pkgroll",