@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 +16 -6
- package/dist/index.js +203 -289
- package/package.json +7 -8
package/dist/index.d.ts
CHANGED
|
@@ -1,15 +1,25 @@
|
|
|
1
|
-
import {
|
|
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
|
-
|
|
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 {
|
|
35
|
+
export { embed, embedMany, generateText, streamText };
|
|
36
|
+
export type { TelemetryOptions, WithTelemetry };
|
package/dist/index.js
CHANGED
|
@@ -1,94 +1,19 @@
|
|
|
1
|
-
import {
|
|
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
|
-
|
|
6
|
-
|
|
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
|
|
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
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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
|
|
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
|
-
"
|
|
152
|
-
"
|
|
153
|
-
"
|
|
154
|
-
"
|
|
155
|
-
"
|
|
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:
|
|
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("
|
|
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
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
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
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
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
|
-
|
|
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
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
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
|
-
} :
|
|
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.
|
|
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({
|
|
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({
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
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.
|
|
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.
|
|
33
|
+
"xsai": "~0.4.1"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
|
-
"@
|
|
37
|
-
"@opentelemetry/sdk-trace-
|
|
38
|
-
"
|
|
39
|
-
"
|
|
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
|
|
43
|
+
"test": "vitest run"
|
|
45
44
|
},
|
|
46
45
|
"main": "./dist/index.js",
|
|
47
46
|
"types": "./dist/index.d.ts"
|