ai 7.0.123 → 7.0.124

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.
@@ -1,21 +1,34 @@
1
1
  import type { JSONObject } from '@ai-sdk/provider';
2
2
  import {
3
+ createIdGenerator,
3
4
  detectMediaType,
4
5
  withUserAgentSuffix,
5
6
  type ProviderOptions,
6
7
  } from '@ai-sdk/provider-utils';
7
8
  import { NoTranscriptGeneratedError } from '../error/no-transcript-generated-error';
8
9
  import { logWarnings } from '../logger/log-warnings';
10
+ import { resolveTranscriptionModel } from '../model/resolve-model';
9
11
  import type { DataContent } from '../prompt';
10
12
  import { convertDataContentToUint8Array } from '../prompt/data-content';
13
+ import { createTelemetryDispatcher } from '../telemetry/create-telemetry-dispatcher';
14
+ import type { TelemetryOptions } from '../telemetry/telemetry-options';
11
15
  import type { TranscriptionModel } from '../types/transcription-model';
12
16
  import type { TranscriptionModelResponseMetadata } from '../types/transcription-model-response-metadata';
13
17
  import { createDownload } from '../util/download/create-download';
18
+ import { notify } from '../util/notify';
14
19
  import { prepareRetries } from '../util/prepare-retries';
15
20
  import type { TranscriptionResult } from './transcribe-result';
16
21
  import { VERSION } from '../version';
17
- import { resolveTranscriptionModel } from '../model/resolve-model';
18
22
  import type { Warning } from '../types';
23
+ import type {
24
+ TranscriptionEndEvent,
25
+ TranscriptionStartEvent,
26
+ } from './transcription-events';
27
+
28
+ const originalGenerateCallId = createIdGenerator({
29
+ prefix: 'call',
30
+ size: 24,
31
+ });
19
32
  /**
20
33
  * Generates transcripts using a transcription model.
21
34
  *
@@ -26,6 +39,7 @@ import type { Warning } from '../types';
26
39
  * @param maxRetries - Maximum number of retries. Set to 0 to disable retries. Default: 2.
27
40
  * @param abortSignal - An optional abort signal that can be used to cancel the call.
28
41
  * @param headers - Additional HTTP headers to be sent with the request. Only applicable for HTTP-based providers.
42
+ * @param telemetry - Optional telemetry configuration.
29
43
  *
30
44
  * @returns A result object that contains the generated transcript.
31
45
  */
@@ -38,7 +52,9 @@ export async function transcribe({
38
52
  maxRetries: maxRetriesArg,
39
53
  abortSignal,
40
54
  headers,
55
+ telemetry,
41
56
  download: downloadFn = defaultDownload,
57
+ _internal: { generateCallId = originalGenerateCallId } = {},
42
58
  }: {
43
59
  /**
44
60
  * The transcription model to use.
@@ -84,6 +100,11 @@ export async function transcribe({
84
100
  */
85
101
  headers?: Record<string, string>;
86
102
 
103
+ /**
104
+ * Optional telemetry configuration.
105
+ */
106
+ telemetry?: TelemetryOptions;
107
+
87
108
  /**
88
109
  * Custom download function for fetching audio from URLs.
89
110
  * Use `createDownload()` from `ai` to create a download function with custom size limits.
@@ -94,13 +115,20 @@ export async function transcribe({
94
115
  url: URL;
95
116
  abortSignal?: AbortSignal;
96
117
  }) => Promise<{ data: Uint8Array; mediaType: string | undefined }>;
118
+
119
+ /**
120
+ * Internal. For test use only. May change without notice.
121
+ */
122
+ _internal?: {
123
+ generateCallId?: () => string;
124
+ };
97
125
  }): Promise<TranscriptionResult> {
98
126
  const resolvedModel = resolveTranscriptionModel(model);
99
127
  if (!resolvedModel) {
100
128
  throw new Error('Model could not be resolved');
101
129
  }
102
130
 
103
- const { retry } = prepareRetries({
131
+ const { maxRetries, retry } = prepareRetries({
104
132
  maxRetries: maxRetriesArg,
105
133
  abortSignal,
106
134
  });
@@ -110,44 +138,169 @@ export async function transcribe({
110
138
  `ai/${VERSION}`,
111
139
  );
112
140
 
113
- const audioData =
114
- audio instanceof URL
115
- ? (await downloadFn({ url: audio, abortSignal })).data
116
- : convertDataContentToUint8Array(audio);
117
-
118
- const result = await retry(() =>
119
- resolvedModel.doGenerate({
120
- audio: audioData,
121
- abortSignal,
122
- headers: headersWithUserAgent,
123
- providerOptions,
124
- mediaType:
125
- detectMediaType({
126
- data: audioData,
127
- topLevelType: 'audio',
128
- }) ?? 'audio/wav',
129
- }),
130
- );
141
+ const callId = generateCallId();
142
+ const telemetryDispatcher = createTelemetryDispatcher({ telemetry });
143
+ const runInTracingChannelSpan =
144
+ telemetryDispatcher.runInTracingChannelSpan ??
145
+ (async <T>({ execute }: { execute: () => PromiseLike<T> }) =>
146
+ await execute());
131
147
 
132
- logWarnings({
133
- warnings: result.warnings,
148
+ const createStartEvent = ({
149
+ byteLength,
150
+ mediaType,
151
+ }: {
152
+ byteLength: number | undefined;
153
+ mediaType: string | undefined;
154
+ }): TranscriptionStartEvent => ({
155
+ callId,
156
+ operationId: 'ai.transcribe',
134
157
  provider: resolvedModel.provider,
135
- model: resolvedModel.modelId,
158
+ modelId: resolvedModel.modelId,
159
+ audio: {
160
+ byteLength,
161
+ mediaType,
162
+ },
163
+ inputAudioFormat: undefined,
164
+ maxRetries,
165
+ headers,
166
+ providerOptions,
136
167
  });
137
168
 
138
- if (!result.text) {
139
- throw new NoTranscriptGeneratedError({ responses: [result.response] });
169
+ const executeWithStartEvent = async <T>({
170
+ startEvent,
171
+ execute,
172
+ }: {
173
+ startEvent: TranscriptionStartEvent;
174
+ execute: () => PromiseLike<T>;
175
+ }) =>
176
+ await runInTracingChannelSpan({
177
+ type: 'transcribe',
178
+ event: startEvent,
179
+ execute: async () => {
180
+ await notify({
181
+ event: startEvent,
182
+ callbacks: [telemetryDispatcher.onStart],
183
+ });
184
+
185
+ return await execute();
186
+ },
187
+ });
188
+
189
+ let preparationError: unknown;
190
+ let preparationFailed = false;
191
+ let audioData: Uint8Array;
192
+ let downloadedMediaType: string | undefined;
193
+
194
+ try {
195
+ if (audio instanceof URL) {
196
+ const downloadResult = await downloadFn({ url: audio, abortSignal });
197
+ audioData = downloadResult.data;
198
+ downloadedMediaType = downloadResult.mediaType;
199
+ } else {
200
+ audioData = convertDataContentToUint8Array(audio);
201
+ }
202
+ } catch (error) {
203
+ preparationFailed = true;
204
+ preparationError = error;
205
+ audioData = new Uint8Array();
206
+ }
207
+
208
+ if (preparationFailed) {
209
+ const startEvent = createStartEvent({
210
+ byteLength: undefined,
211
+ mediaType: undefined,
212
+ });
213
+
214
+ try {
215
+ return await executeWithStartEvent({
216
+ startEvent,
217
+ execute: () => {
218
+ throw preparationError;
219
+ },
220
+ });
221
+ } catch (error) {
222
+ await telemetryDispatcher.onError?.({ callId, error });
223
+ throw error;
224
+ }
140
225
  }
141
226
 
142
- return new DefaultTranscriptionResult({
143
- text: result.text,
144
- segments: result.segments,
145
- language: result.language,
146
- durationInSeconds: result.durationInSeconds,
147
- warnings: result.warnings,
148
- responses: [result.response],
149
- providerMetadata: result.providerMetadata,
227
+ const mediaType =
228
+ downloadedMediaType ??
229
+ detectMediaType({
230
+ data: audioData,
231
+ topLevelType: 'audio',
232
+ }) ??
233
+ 'audio/wav';
234
+ const startEvent = createStartEvent({
235
+ byteLength: audioData.byteLength,
236
+ mediaType,
150
237
  });
238
+
239
+ try {
240
+ return await executeWithStartEvent({
241
+ startEvent,
242
+ execute: async () => {
243
+ const result = await retry(() =>
244
+ resolvedModel.doGenerate({
245
+ audio: audioData,
246
+ abortSignal,
247
+ headers: headersWithUserAgent,
248
+ providerOptions,
249
+ mediaType,
250
+ }),
251
+ );
252
+
253
+ logWarnings({
254
+ warnings: result.warnings,
255
+ provider: resolvedModel.provider,
256
+ model: resolvedModel.modelId,
257
+ });
258
+
259
+ if (!result.text) {
260
+ throw new NoTranscriptGeneratedError({
261
+ responses: [result.response],
262
+ });
263
+ }
264
+
265
+ const endEvent: TranscriptionEndEvent = {
266
+ callId,
267
+ operationId: 'ai.transcribe',
268
+ provider: resolvedModel.provider,
269
+ modelId: resolvedModel.modelId,
270
+ audio: {
271
+ byteLength: audioData.byteLength,
272
+ mediaType,
273
+ },
274
+ text: result.text,
275
+ segments: result.segments,
276
+ language: result.language,
277
+ durationInSeconds: result.durationInSeconds,
278
+ usage: result.usage,
279
+ warnings: result.warnings,
280
+ providerMetadata: result.providerMetadata,
281
+ response: result.response,
282
+ };
283
+
284
+ await notify({
285
+ event: endEvent,
286
+ callbacks: [telemetryDispatcher.onEnd],
287
+ });
288
+
289
+ return new DefaultTranscriptionResult({
290
+ text: result.text,
291
+ segments: result.segments,
292
+ language: result.language,
293
+ durationInSeconds: result.durationInSeconds,
294
+ warnings: result.warnings,
295
+ responses: [result.response],
296
+ providerMetadata: result.providerMetadata,
297
+ });
298
+ },
299
+ });
300
+ } catch (error) {
301
+ await telemetryDispatcher.onError?.({ callId, error });
302
+ throw error;
303
+ }
151
304
  }
152
305
 
153
306
  class DefaultTranscriptionResult implements TranscriptionResult {
@@ -0,0 +1,111 @@
1
+ import type { JSONObject, SharedV4AudioFormat } from '@ai-sdk/provider';
2
+ import type { ProviderOptions } from '@ai-sdk/provider-utils';
3
+ import type { ProviderMetadata } from '../types';
4
+ import type { TranscriptionModelResponseMetadata } from '../types/transcription-model-response-metadata';
5
+ import type { Warning } from '../types/warning';
6
+
7
+ /**
8
+ * Event passed to telemetry integrations when transcription begins.
9
+ */
10
+ type TranscriptionStartEventFor<OPERATION_ID extends string> = {
11
+ /** Unique identifier for this transcription call. */
12
+ readonly callId: string;
13
+
14
+ /** Identifies the operation type (`ai.transcribe` or `ai.streamTranscribe`). */
15
+ readonly operationId: OPERATION_ID;
16
+
17
+ /** The provider identifier. */
18
+ readonly provider: string;
19
+
20
+ /** The specific model identifier. */
21
+ readonly modelId: string;
22
+
23
+ /**
24
+ * Metadata about the input audio. The byte length is known immediately for
25
+ * non-streaming transcription and is reported on the end event for streams.
26
+ */
27
+ readonly audio: {
28
+ readonly byteLength: number | undefined;
29
+ /**
30
+ * The media type, when it is known before input preparation completes.
31
+ * URL downloads and invalid inline inputs can begin without this metadata.
32
+ */
33
+ readonly mediaType: string | undefined;
34
+ };
35
+
36
+ /** The raw input format for streaming transcription. */
37
+ readonly inputAudioFormat: SharedV4AudioFormat | undefined;
38
+
39
+ /** Maximum number of retries for non-streaming transcription. */
40
+ readonly maxRetries: number | undefined;
41
+
42
+ /** Additional HTTP or WebSocket headers sent with the request. */
43
+ readonly headers: Record<string, string> | undefined;
44
+
45
+ /** Additional provider-specific options. */
46
+ readonly providerOptions: ProviderOptions;
47
+ };
48
+
49
+ export type TranscriptionStartEvent =
50
+ TranscriptionStartEventFor<'ai.transcribe'>;
51
+
52
+ export type StreamTranscriptionStartEvent =
53
+ TranscriptionStartEventFor<'ai.streamTranscribe'>;
54
+
55
+ /**
56
+ * Event passed to telemetry integrations when transcription completes.
57
+ *
58
+ * Raw audio is intentionally not included.
59
+ */
60
+ type TranscriptionEndEventFor<OPERATION_ID extends string> = {
61
+ /** Unique identifier for this transcription call. */
62
+ readonly callId: string;
63
+
64
+ /** Identifies the operation type (`ai.transcribe` or `ai.streamTranscribe`). */
65
+ readonly operationId: OPERATION_ID;
66
+
67
+ /** The provider identifier. */
68
+ readonly provider: string;
69
+
70
+ /** The specific model identifier. */
71
+ readonly modelId: string;
72
+
73
+ /** Metadata about the input audio. */
74
+ readonly audio: {
75
+ readonly byteLength: number;
76
+ readonly mediaType: string;
77
+ };
78
+
79
+ /** The complete transcript. */
80
+ readonly text: string;
81
+
82
+ /** Transcript segments with timing information. */
83
+ readonly segments: Array<{
84
+ readonly text: string;
85
+ readonly startSecond: number;
86
+ readonly endSecond: number;
87
+ }>;
88
+
89
+ /** The detected language. */
90
+ readonly language: string | undefined;
91
+
92
+ /** The duration of the input audio, when available. */
93
+ readonly durationInSeconds: number | undefined;
94
+
95
+ /** Usage reported by the provider, when available. */
96
+ readonly usage: JSONObject | undefined;
97
+
98
+ /** Warnings from the transcription model. */
99
+ readonly warnings: Array<Warning>;
100
+
101
+ /** Optional provider-specific metadata. */
102
+ readonly providerMetadata: ProviderMetadata | undefined;
103
+
104
+ /** Response metadata from the provider. */
105
+ readonly response: TranscriptionModelResponseMetadata;
106
+ };
107
+
108
+ export type TranscriptionEndEvent = TranscriptionEndEventFor<'ai.transcribe'>;
109
+
110
+ export type StreamTranscriptionEndEvent =
111
+ TranscriptionEndEventFor<'ai.streamTranscribe'>;