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.
- package/CHANGELOG.md +14 -0
- package/dist/index.d.ts +191 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +383 -91
- package/dist/index.js.map +1 -1
- package/dist/internal/index.d.ts +199 -8
- package/dist/internal/index.d.ts.map +1 -1
- package/dist/internal/index.js +9 -3
- package/dist/internal/index.js.map +1 -1
- package/docs/03-ai-sdk-core/36-transcription.mdx +36 -0
- package/docs/03-ai-sdk-core/37-speech.mdx +21 -0
- package/docs/03-ai-sdk-core/60-telemetry.mdx +49 -1
- package/docs/07-reference/01-ai-sdk-core/11-stream-transcribe.mdx +7 -0
- package/docs/07-reference/01-ai-sdk-core/11-transcribe.mdx +7 -0
- package/docs/07-reference/01-ai-sdk-core/12-generate-speech.mdx +7 -0
- package/package.json +12 -12
- package/src/generate-speech/generate-speech.ts +163 -38
- package/src/generate-speech/index.ts +4 -0
- package/src/generate-speech/speech-events.ts +91 -0
- package/src/telemetry/create-telemetry-dispatcher.ts +18 -5
- package/src/telemetry/telemetry.ts +38 -6
- package/src/telemetry/tracing-channel.ts +3 -0
- package/src/transcribe/index.ts +6 -0
- package/src/transcribe/stream-transcribe.ts +182 -31
- package/src/transcribe/transcribe.ts +186 -33
- package/src/transcribe/transcription-events.ts +111 -0
|
@@ -30,6 +30,42 @@ const language = transcript.language; // language of the transcript e.g. "en", i
|
|
|
30
30
|
const durationInSeconds = transcript.durationInSeconds; // duration of the transcript in seconds, if available
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
+
## Telemetry
|
|
34
|
+
|
|
35
|
+
`transcribe` and `experimental_streamTranscribe` accept the standard
|
|
36
|
+
`telemetry` option. Registered integrations receive start, end, and error
|
|
37
|
+
events with the provider and model, audio byte length and media type, final
|
|
38
|
+
transcript, warnings, response metadata, provider metadata, and usage when the
|
|
39
|
+
provider reports it. Because streaming transcription is experimental, custom
|
|
40
|
+
integrations receive its lifecycle through
|
|
41
|
+
`experimental_onStreamTranscriptionStart` and
|
|
42
|
+
`experimental_onStreamTranscriptionEnd`; errors continue to use `onError`.
|
|
43
|
+
Integrations that wrap or delegate to another telemetry integration must
|
|
44
|
+
forward these experimental callbacks explicitly for streaming transcription
|
|
45
|
+
to be observed.
|
|
46
|
+
|
|
47
|
+
```ts highlight="5-7"
|
|
48
|
+
const transcript = await transcribe({
|
|
49
|
+
model: openai.transcription('gpt-4o-transcribe'),
|
|
50
|
+
audio: await readFile('audio.mp3'),
|
|
51
|
+
telemetry: {
|
|
52
|
+
functionId: 'transcribe-customer-call',
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Raw audio is not included in telemetry events. For streaming transcription,
|
|
58
|
+
the final byte length is emitted only after the input stream is consumed and
|
|
59
|
+
the transcription finishes. Use `recordInputs: false` or
|
|
60
|
+
`recordOutputs: false` to prevent integrations from recording sensitive input
|
|
61
|
+
or output fields.
|
|
62
|
+
|
|
63
|
+
For URL audio, the downloaded media type is passed to the provider and reported
|
|
64
|
+
on successful telemetry events. If downloading or decoding input audio fails,
|
|
65
|
+
the start event is still emitted before the correlated error event, but its
|
|
66
|
+
audio byte length and media type can be undefined because preparation did not
|
|
67
|
+
complete.
|
|
68
|
+
|
|
33
69
|
## Streaming Transcription
|
|
34
70
|
|
|
35
71
|
<Note type="warning">Streaming transcription is an experimental feature.</Note>
|
|
@@ -19,6 +19,27 @@ const result = await generateSpeech({
|
|
|
19
19
|
});
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
+
## Telemetry
|
|
23
|
+
|
|
24
|
+
`generateSpeech` accepts the standard `telemetry` option. Registered
|
|
25
|
+
integrations receive start, end, and error events with the provider and model,
|
|
26
|
+
source text, generated audio byte length, media type and format, warnings,
|
|
27
|
+
response metadata, provider metadata, and usage when the provider reports it.
|
|
28
|
+
Raw generated audio is not included in telemetry events.
|
|
29
|
+
|
|
30
|
+
```ts highlight="5-7"
|
|
31
|
+
const result = await generateSpeech({
|
|
32
|
+
model: openai.speech('gpt-4o-mini-tts'),
|
|
33
|
+
text: 'Hello from the AI SDK!',
|
|
34
|
+
telemetry: {
|
|
35
|
+
functionId: 'speak-greeting',
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Use `recordInputs: false` or `recordOutputs: false` when the text or output
|
|
41
|
+
metadata should not be recorded by telemetry integrations.
|
|
42
|
+
|
|
22
43
|
To access the generated audio:
|
|
23
44
|
|
|
24
45
|
```ts
|
|
@@ -53,7 +53,10 @@ For Node.js applications (without Next.js), register the integration at the top
|
|
|
53
53
|
|
|
54
54
|
### Step 2: Enabling Telemetry
|
|
55
55
|
|
|
56
|
-
Once a telemetry integration is registered,
|
|
56
|
+
Once a telemetry integration is registered, supported AI SDK calls emit
|
|
57
|
+
telemetry events by default. This includes text and object generation,
|
|
58
|
+
embeddings, reranking, speech generation, and both non-streaming and streaming
|
|
59
|
+
transcription.
|
|
57
60
|
You can still pass `telemetry` to attach metadata (like `functionId`) or to opt out of a specific call:
|
|
58
61
|
|
|
59
62
|
```ts highlight="4-6"
|
|
@@ -71,6 +74,34 @@ By default, both inputs and outputs are recorded. You can disable them by settin
|
|
|
71
74
|
Disabling the recording of inputs and outputs can be useful for privacy, data transfer, and performance reasons.
|
|
72
75
|
You might for example want to disable recording inputs if they contain sensitive information.
|
|
73
76
|
|
|
77
|
+
For `generateSpeech`, input telemetry contains the source text and output
|
|
78
|
+
telemetry contains the generated audio byte length, media type, and format.
|
|
79
|
+
For `transcribe` and `experimental_streamTranscribe`, telemetry contains the
|
|
80
|
+
input audio byte length and media type plus the output transcript. Raw audio is
|
|
81
|
+
never included in telemetry events. Streaming transcription reports its final
|
|
82
|
+
byte length when the audio stream has been consumed and the transcription
|
|
83
|
+
finishes.
|
|
84
|
+
|
|
85
|
+
Custom integrations receive stable `generateSpeech` and `transcribe`
|
|
86
|
+
operations through `onStart` and `onEnd`. To keep the experimental API isolated,
|
|
87
|
+
`experimental_streamTranscribe` uses
|
|
88
|
+
`experimental_onStreamTranscriptionStart` and
|
|
89
|
+
`experimental_onStreamTranscriptionEnd`; all three operations report failures
|
|
90
|
+
through `onError`.
|
|
91
|
+
|
|
92
|
+
An integration that wraps or delegates to another integration must explicitly
|
|
93
|
+
forward the experimental streaming transcription callbacks. Forwarding only
|
|
94
|
+
the stable `onStart` and `onEnd` callbacks does not observe
|
|
95
|
+
`experimental_streamTranscribe`.
|
|
96
|
+
|
|
97
|
+
The built-in OpenTelemetry integrations record numeric provider usage by
|
|
98
|
+
default, preserving each reported unit. The modern integration uses attributes
|
|
99
|
+
such as `gen_ai.usage.characters`, `gen_ai.usage.seconds`, and
|
|
100
|
+
`gen_ai.usage.input_tokens`; `LegacyOpenTelemetry` uses the corresponding
|
|
101
|
+
`ai.usage.*` attributes. The provider-native usage object remains available to
|
|
102
|
+
callbacks, and can also be included as the supplemental `ai.response.usage`
|
|
103
|
+
attribute by enabling `usage: true`.
|
|
104
|
+
|
|
74
105
|
### Opting out
|
|
75
106
|
|
|
76
107
|
Telemetry is opt-out. To disable telemetry for a specific call, set `isEnabled: false`:
|
|
@@ -550,6 +581,23 @@ For `rerank`, the integration records spans with `CLIENT` kind:
|
|
|
550
581
|
- `gen_ai.provider.name`: the provider
|
|
551
582
|
- `gen_ai.request.model`: the model ID
|
|
552
583
|
|
|
584
|
+
#### generateSpeech / transcribe / experimental_streamTranscribe
|
|
585
|
+
|
|
586
|
+
Each call records a single `CLIENT` span named `{operationId} {modelId}`, where
|
|
587
|
+
`operationId` is `ai.generateSpeech`, `ai.transcribe`, or `ai.streamTranscribe`.
|
|
588
|
+
|
|
589
|
+
- `gen_ai.output.type`: `"speech"` for speech generation and `"text"` for transcription.
|
|
590
|
+
- `gen_ai.request.stream`: `true` for streaming transcription and `false` otherwise.
|
|
591
|
+
- `gen_ai.input.messages`: speech generation input text as a user message with a text part, when `recordInputs` is enabled.
|
|
592
|
+
- `gen_ai.output.messages`: the completed transcript as an assistant message with a text part, when `recordOutputs` is enabled. Streaming transcription records this once on completion.
|
|
593
|
+
|
|
594
|
+
The existing `ai.request.text` and `ai.response.text` attributes are also retained.
|
|
595
|
+
Audio byte length, media type, and output format remain in the
|
|
596
|
+
`ai.request.audio.*` and `ai.response.audio.*` attributes, subject to the
|
|
597
|
+
corresponding input/output recording controls. Raw audio is not captured, so
|
|
598
|
+
transcription input and speech output do not populate message attributes with
|
|
599
|
+
audio content or placeholder audio parts.
|
|
600
|
+
|
|
553
601
|
#### experimental_evaluate
|
|
554
602
|
|
|
555
603
|
For `experimental_evaluate`, the integration records two spans with `CLIENT`
|
|
@@ -88,6 +88,13 @@ console.log(await result.text);
|
|
|
88
88
|
description:
|
|
89
89
|
'When true, the provider includes raw provider chunks in the stream as `raw` parts.',
|
|
90
90
|
},
|
|
91
|
+
{
|
|
92
|
+
name: 'telemetry',
|
|
93
|
+
type: 'TelemetryOptions',
|
|
94
|
+
isOptional: true,
|
|
95
|
+
description:
|
|
96
|
+
'Telemetry configuration. Experimental lifecycle callbacks use experimental_onStreamTranscriptionStart and experimental_onStreamTranscriptionEnd. The end event is emitted after the audio stream is consumed and includes the final input byte length.',
|
|
97
|
+
},
|
|
91
98
|
]}
|
|
92
99
|
/>
|
|
93
100
|
|
|
@@ -64,6 +64,13 @@ console.log(transcript);
|
|
|
64
64
|
isOptional: true,
|
|
65
65
|
description: 'Additional HTTP headers for the request.',
|
|
66
66
|
},
|
|
67
|
+
{
|
|
68
|
+
name: 'telemetry',
|
|
69
|
+
type: 'TelemetryOptions',
|
|
70
|
+
isOptional: true,
|
|
71
|
+
description:
|
|
72
|
+
'Telemetry configuration. Supports functionId, recordInputs, recordOutputs, isEnabled, and per-call integrations.',
|
|
73
|
+
},
|
|
67
74
|
{
|
|
68
75
|
name: 'download',
|
|
69
76
|
type: '(options: { url: URL; abortSignal?: AbortSignal }) => Promise<{ data: Uint8Array; mediaType: string | undefined }>',
|
|
@@ -124,6 +124,13 @@ const { audio } = await generateSpeech({
|
|
|
124
124
|
isOptional: true,
|
|
125
125
|
description: 'Additional HTTP headers for the request.',
|
|
126
126
|
},
|
|
127
|
+
{
|
|
128
|
+
name: 'telemetry',
|
|
129
|
+
type: 'TelemetryOptions',
|
|
130
|
+
isOptional: true,
|
|
131
|
+
description:
|
|
132
|
+
'Telemetry configuration. Supports functionId, recordInputs, recordOutputs, isEnabled, and per-call integrations.',
|
|
133
|
+
},
|
|
127
134
|
]}
|
|
128
135
|
/>
|
|
129
136
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai",
|
|
3
|
-
"version": "7.0.
|
|
3
|
+
"version": "7.0.124",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "AI SDK by Vercel - build apps like ChatGPT, Claude, Gemini, and more with a single interface for any model using the Vercel AI Gateway or go direct to OpenAI, Anthropic, Google, or any other model provider.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -42,20 +42,20 @@
|
|
|
42
42
|
}
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@ai-sdk/gateway": "4.0.
|
|
46
|
-
"@ai-sdk/provider": "4.0.
|
|
47
|
-
"@ai-sdk/provider-utils": "5.0.
|
|
45
|
+
"@ai-sdk/gateway": "4.0.102",
|
|
46
|
+
"@ai-sdk/provider": "4.0.21",
|
|
47
|
+
"@ai-sdk/provider-utils": "5.0.53"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
|
-
"@ai-sdk/amazon-bedrock": "5.0.
|
|
51
|
-
"@ai-sdk/deepseek": "3.0.
|
|
52
|
-
"@ai-sdk/google": "4.0.
|
|
53
|
-
"@ai-sdk/groq": "4.0.
|
|
54
|
-
"@ai-sdk/huggingface": "2.0.
|
|
55
|
-
"@ai-sdk/moonshotai": "3.0.
|
|
56
|
-
"@ai-sdk/openai": "4.0.
|
|
50
|
+
"@ai-sdk/amazon-bedrock": "5.0.103",
|
|
51
|
+
"@ai-sdk/deepseek": "3.0.58",
|
|
52
|
+
"@ai-sdk/google": "4.0.87",
|
|
53
|
+
"@ai-sdk/groq": "4.0.54",
|
|
54
|
+
"@ai-sdk/huggingface": "2.0.61",
|
|
55
|
+
"@ai-sdk/moonshotai": "3.0.62",
|
|
56
|
+
"@ai-sdk/openai": "4.0.83",
|
|
57
57
|
"@ai-sdk/test-server": "2.0.3",
|
|
58
|
-
"@ai-sdk/xai": "5.0.
|
|
58
|
+
"@ai-sdk/xai": "5.0.14",
|
|
59
59
|
"@edge-runtime/vm": "^5.0.0",
|
|
60
60
|
"@smithy/eventstream-codec": "^4.3.3",
|
|
61
61
|
"@smithy/util-utf8": "^4.3.3",
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { JSONObject } from '@ai-sdk/provider';
|
|
2
2
|
import {
|
|
3
|
+
createIdGenerator,
|
|
3
4
|
detectMediaType,
|
|
4
5
|
withUserAgentSuffix,
|
|
5
6
|
type ProviderOptions,
|
|
@@ -7,16 +8,28 @@ import {
|
|
|
7
8
|
import { NoSpeechGeneratedError } from '../error/no-speech-generated-error';
|
|
8
9
|
import { logWarnings } from '../logger/log-warnings';
|
|
9
10
|
import { resolveSpeechModel } from '../model/resolve-model';
|
|
11
|
+
import { createTelemetryDispatcher } from '../telemetry/create-telemetry-dispatcher';
|
|
12
|
+
import type { TelemetryOptions } from '../telemetry/telemetry-options';
|
|
10
13
|
import type { SpeechModel } from '../types/speech-model';
|
|
11
14
|
import type { SpeechModelResponseMetadata } from '../types/speech-model-response-metadata';
|
|
12
15
|
import type { Warning } from '../types/warning';
|
|
13
16
|
import { prepareRetries } from '../util/prepare-retries';
|
|
17
|
+
import { notify } from '../util/notify';
|
|
14
18
|
import { VERSION } from '../version';
|
|
15
19
|
import type { SpeechResult } from './generate-speech-result';
|
|
16
20
|
import {
|
|
17
21
|
DefaultGeneratedAudioFile,
|
|
18
22
|
type GeneratedAudioFile,
|
|
19
23
|
} from './generated-audio-file';
|
|
24
|
+
import type {
|
|
25
|
+
GenerateSpeechEndEvent,
|
|
26
|
+
GenerateSpeechStartEvent,
|
|
27
|
+
} from './speech-events';
|
|
28
|
+
|
|
29
|
+
const originalGenerateCallId = createIdGenerator({
|
|
30
|
+
prefix: 'call',
|
|
31
|
+
size: 24,
|
|
32
|
+
});
|
|
20
33
|
/**
|
|
21
34
|
* Generates speech audio using a speech model.
|
|
22
35
|
*
|
|
@@ -32,6 +45,7 @@ import {
|
|
|
32
45
|
* @param maxRetries - Maximum number of retries. Set to 0 to disable retries. Default: 2.
|
|
33
46
|
* @param abortSignal - An optional abort signal that can be used to cancel the call.
|
|
34
47
|
* @param headers - Additional HTTP headers to be sent with the request. Only applicable for HTTP-based providers.
|
|
48
|
+
* @param telemetry - Optional telemetry configuration.
|
|
35
49
|
*
|
|
36
50
|
* @returns A result object that contains the generated audio data.
|
|
37
51
|
*/
|
|
@@ -47,6 +61,8 @@ export async function generateSpeech({
|
|
|
47
61
|
maxRetries: maxRetriesArg,
|
|
48
62
|
abortSignal,
|
|
49
63
|
headers,
|
|
64
|
+
telemetry,
|
|
65
|
+
_internal: { generateCallId = originalGenerateCallId } = {},
|
|
50
66
|
}: {
|
|
51
67
|
/**
|
|
52
68
|
* The speech model to use.
|
|
@@ -115,6 +131,18 @@ export async function generateSpeech({
|
|
|
115
131
|
* Only applicable for HTTP-based providers.
|
|
116
132
|
*/
|
|
117
133
|
headers?: Record<string, string>;
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Optional telemetry configuration.
|
|
137
|
+
*/
|
|
138
|
+
telemetry?: TelemetryOptions;
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Internal. For test use only. May change without notice.
|
|
142
|
+
*/
|
|
143
|
+
_internal?: {
|
|
144
|
+
generateCallId?: () => string;
|
|
145
|
+
};
|
|
118
146
|
}): Promise<SpeechResult> {
|
|
119
147
|
const resolvedModel = resolveSpeechModel(model);
|
|
120
148
|
if (!resolvedModel) {
|
|
@@ -126,55 +154,152 @@ export async function generateSpeech({
|
|
|
126
154
|
`ai/${VERSION}`,
|
|
127
155
|
);
|
|
128
156
|
|
|
129
|
-
const { retry } = prepareRetries({
|
|
157
|
+
const { maxRetries, retry } = prepareRetries({
|
|
130
158
|
maxRetries: maxRetriesArg,
|
|
131
159
|
abortSignal,
|
|
132
160
|
});
|
|
133
161
|
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
speed,
|
|
141
|
-
language,
|
|
142
|
-
abortSignal,
|
|
143
|
-
headers: headersWithUserAgent,
|
|
144
|
-
providerOptions,
|
|
145
|
-
}),
|
|
146
|
-
);
|
|
147
|
-
|
|
148
|
-
if (!result.audio || result.audio.length === 0) {
|
|
149
|
-
throw new NoSpeechGeneratedError({ responses: [result.response] });
|
|
150
|
-
}
|
|
162
|
+
const callId = generateCallId();
|
|
163
|
+
const telemetryDispatcher = createTelemetryDispatcher({ telemetry });
|
|
164
|
+
const runInTracingChannelSpan =
|
|
165
|
+
telemetryDispatcher.runInTracingChannelSpan ??
|
|
166
|
+
(async <T>({ execute }: { execute: () => PromiseLike<T> }) =>
|
|
167
|
+
await execute());
|
|
151
168
|
|
|
152
|
-
|
|
153
|
-
|
|
169
|
+
const startEvent: GenerateSpeechStartEvent = {
|
|
170
|
+
callId,
|
|
171
|
+
operationId: 'ai.generateSpeech',
|
|
154
172
|
provider: resolvedModel.provider,
|
|
155
|
-
|
|
156
|
-
|
|
173
|
+
modelId: resolvedModel.modelId,
|
|
174
|
+
text,
|
|
175
|
+
voice,
|
|
176
|
+
outputFormat,
|
|
177
|
+
instructions,
|
|
178
|
+
speed,
|
|
179
|
+
language,
|
|
180
|
+
maxRetries,
|
|
181
|
+
headers,
|
|
182
|
+
providerOptions,
|
|
183
|
+
};
|
|
157
184
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
185
|
+
return await runInTracingChannelSpan({
|
|
186
|
+
type: 'generateSpeech',
|
|
187
|
+
event: startEvent,
|
|
188
|
+
execute: async () => {
|
|
189
|
+
await notify({
|
|
190
|
+
event: startEvent,
|
|
191
|
+
callbacks: [telemetryDispatcher.onStart],
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
try {
|
|
195
|
+
const result = await retry(() =>
|
|
196
|
+
resolvedModel.doGenerate({
|
|
197
|
+
text,
|
|
198
|
+
voice,
|
|
199
|
+
outputFormat,
|
|
200
|
+
instructions,
|
|
201
|
+
speed,
|
|
202
|
+
language,
|
|
203
|
+
abortSignal,
|
|
204
|
+
headers: headersWithUserAgent,
|
|
205
|
+
providerOptions,
|
|
206
|
+
}),
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
if (!result.audio || result.audio.length === 0) {
|
|
210
|
+
throw new NoSpeechGeneratedError({ responses: [result.response] });
|
|
211
|
+
}
|
|
162
212
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
213
|
+
logWarnings({
|
|
214
|
+
warnings: result.warnings,
|
|
215
|
+
provider: resolvedModel.provider,
|
|
216
|
+
model: resolvedModel.modelId,
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
const detectedMediaType = detectMediaType({
|
|
220
|
+
data: result.audio,
|
|
221
|
+
topLevelType: 'audio',
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
const audio = new DefaultGeneratedAudioFile({
|
|
225
|
+
data: result.audio,
|
|
226
|
+
mediaType:
|
|
227
|
+
detectedMediaType ??
|
|
228
|
+
getResponseAudioMediaType(result.response.headers) ??
|
|
229
|
+
getOutputFormatMediaType(outputFormat) ??
|
|
230
|
+
'audio/mp3',
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
if (telemetryDispatcher.onEnd != null) {
|
|
234
|
+
const endEvent: GenerateSpeechEndEvent = {
|
|
235
|
+
callId,
|
|
236
|
+
operationId: 'ai.generateSpeech',
|
|
237
|
+
provider: resolvedModel.provider,
|
|
238
|
+
modelId: resolvedModel.modelId,
|
|
239
|
+
text,
|
|
240
|
+
audio: {
|
|
241
|
+
byteLength: getBase64OrBinaryByteLength(result.audio),
|
|
242
|
+
mediaType: audio.mediaType,
|
|
243
|
+
format: audio.format,
|
|
244
|
+
},
|
|
245
|
+
usage: result.usage,
|
|
246
|
+
warnings: result.warnings,
|
|
247
|
+
providerMetadata: result.providerMetadata,
|
|
248
|
+
response: getTelemetryResponseMetadata(result.response),
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
await notify({
|
|
252
|
+
event: endEvent,
|
|
253
|
+
callbacks: [telemetryDispatcher.onEnd],
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return new DefaultSpeechResult({
|
|
258
|
+
audio,
|
|
259
|
+
warnings: result.warnings,
|
|
260
|
+
responses: [result.response],
|
|
261
|
+
providerMetadata: result.providerMetadata,
|
|
262
|
+
});
|
|
263
|
+
} catch (error) {
|
|
264
|
+
await telemetryDispatcher.onError?.({ callId, error });
|
|
265
|
+
throw error;
|
|
266
|
+
}
|
|
267
|
+
},
|
|
175
268
|
});
|
|
176
269
|
}
|
|
177
270
|
|
|
271
|
+
function getBase64OrBinaryByteLength(audio: string | Uint8Array): number {
|
|
272
|
+
if (audio instanceof Uint8Array) {
|
|
273
|
+
return audio.byteLength;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
let characterCount = 0;
|
|
277
|
+
let lastCharacter = '';
|
|
278
|
+
let secondLastCharacter = '';
|
|
279
|
+
for (const character of audio) {
|
|
280
|
+
if (/\s/.test(character)) {
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
characterCount++;
|
|
284
|
+
secondLastCharacter = lastCharacter;
|
|
285
|
+
lastCharacter = character;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const padding =
|
|
289
|
+
lastCharacter === '=' ? (secondLastCharacter === '=' ? 2 : 1) : 0;
|
|
290
|
+
return Math.floor((characterCount * 3) / 4) - padding;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function getTelemetryResponseMetadata(
|
|
294
|
+
response: SpeechModelResponseMetadata,
|
|
295
|
+
): SpeechModelResponseMetadata {
|
|
296
|
+
return {
|
|
297
|
+
timestamp: response.timestamp,
|
|
298
|
+
modelId: response.modelId,
|
|
299
|
+
headers: response.headers,
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
|
|
178
303
|
function getResponseAudioMediaType(
|
|
179
304
|
headers: Record<string, string> | undefined,
|
|
180
305
|
): string | undefined {
|
|
@@ -4,6 +4,10 @@ import { generateSpeech } from './generate-speech';
|
|
|
4
4
|
export { generateSpeech } from './generate-speech';
|
|
5
5
|
export type { SpeechResult } from './generate-speech-result';
|
|
6
6
|
export type { GeneratedAudioFile } from './generated-audio-file';
|
|
7
|
+
export type {
|
|
8
|
+
GenerateSpeechEndEvent,
|
|
9
|
+
GenerateSpeechStartEvent,
|
|
10
|
+
} from './speech-events';
|
|
7
11
|
|
|
8
12
|
// deprecated exports
|
|
9
13
|
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import type { JSONObject } from '@ai-sdk/provider';
|
|
2
|
+
import type { ProviderOptions } from '@ai-sdk/provider-utils';
|
|
3
|
+
import type { ProviderMetadata } from '../types';
|
|
4
|
+
import type { SpeechModelResponseMetadata } from '../types/speech-model-response-metadata';
|
|
5
|
+
import type { Warning } from '../types/warning';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Event passed to telemetry integrations when speech generation begins.
|
|
9
|
+
*/
|
|
10
|
+
export type GenerateSpeechStartEvent = {
|
|
11
|
+
/** Unique identifier for this speech generation call. */
|
|
12
|
+
readonly callId: string;
|
|
13
|
+
|
|
14
|
+
/** Identifies the operation type (`ai.generateSpeech`). */
|
|
15
|
+
readonly operationId: 'ai.generateSpeech';
|
|
16
|
+
|
|
17
|
+
/** The provider identifier. */
|
|
18
|
+
readonly provider: string;
|
|
19
|
+
|
|
20
|
+
/** The specific model identifier. */
|
|
21
|
+
readonly modelId: string;
|
|
22
|
+
|
|
23
|
+
/** The text to convert to speech. */
|
|
24
|
+
readonly text: string;
|
|
25
|
+
|
|
26
|
+
/** The requested voice. */
|
|
27
|
+
readonly voice: string | undefined;
|
|
28
|
+
|
|
29
|
+
/** The requested output format. */
|
|
30
|
+
readonly outputFormat: string | undefined;
|
|
31
|
+
|
|
32
|
+
/** Additional speech instructions. */
|
|
33
|
+
readonly instructions: string | undefined;
|
|
34
|
+
|
|
35
|
+
/** The requested speech speed. */
|
|
36
|
+
readonly speed: number | undefined;
|
|
37
|
+
|
|
38
|
+
/** The requested language. */
|
|
39
|
+
readonly language: string | undefined;
|
|
40
|
+
|
|
41
|
+
/** Maximum number of retries for failed requests. */
|
|
42
|
+
readonly maxRetries: number;
|
|
43
|
+
|
|
44
|
+
/** Additional HTTP headers sent with the request. */
|
|
45
|
+
readonly headers: Record<string, string> | undefined;
|
|
46
|
+
|
|
47
|
+
/** Additional provider-specific options. */
|
|
48
|
+
readonly providerOptions: ProviderOptions;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Event passed to telemetry integrations when speech generation completes.
|
|
53
|
+
*
|
|
54
|
+
* Raw audio is intentionally not included. Integrations receive only its size,
|
|
55
|
+
* media type, and format.
|
|
56
|
+
*/
|
|
57
|
+
export type GenerateSpeechEndEvent = {
|
|
58
|
+
/** Unique identifier for this speech generation call. */
|
|
59
|
+
readonly callId: string;
|
|
60
|
+
|
|
61
|
+
/** Identifies the operation type (`ai.generateSpeech`). */
|
|
62
|
+
readonly operationId: 'ai.generateSpeech';
|
|
63
|
+
|
|
64
|
+
/** The provider identifier. */
|
|
65
|
+
readonly provider: string;
|
|
66
|
+
|
|
67
|
+
/** The specific model identifier. */
|
|
68
|
+
readonly modelId: string;
|
|
69
|
+
|
|
70
|
+
/** The text that was converted to speech. */
|
|
71
|
+
readonly text: string;
|
|
72
|
+
|
|
73
|
+
/** Metadata about the generated audio. */
|
|
74
|
+
readonly audio: {
|
|
75
|
+
readonly byteLength: number;
|
|
76
|
+
readonly mediaType: string;
|
|
77
|
+
readonly format: string;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
/** Usage reported by the provider, when available. */
|
|
81
|
+
readonly usage: JSONObject | undefined;
|
|
82
|
+
|
|
83
|
+
/** Warnings from the speech model. */
|
|
84
|
+
readonly warnings: Array<Warning>;
|
|
85
|
+
|
|
86
|
+
/** Optional provider-specific metadata. */
|
|
87
|
+
readonly providerMetadata: ProviderMetadata | undefined;
|
|
88
|
+
|
|
89
|
+
/** Response metadata from the provider. */
|
|
90
|
+
readonly response: SpeechModelResponseMetadata;
|
|
91
|
+
};
|
|
@@ -142,7 +142,7 @@ export function createTelemetryDispatcher({
|
|
|
142
142
|
|
|
143
143
|
const mergeTelemetryCallback = <KEY extends TelemetryCallbackKey>(
|
|
144
144
|
key: KEY,
|
|
145
|
-
): Callback<TelemetryEvent<KEY>> => {
|
|
145
|
+
): Callback<TelemetryEvent<KEY>> | undefined => {
|
|
146
146
|
const integrationCallbacks = (
|
|
147
147
|
integrations
|
|
148
148
|
.map(integration => integration[key]?.bind(integration))
|
|
@@ -157,6 +157,10 @@ export function createTelemetryDispatcher({
|
|
|
157
157
|
>,
|
|
158
158
|
);
|
|
159
159
|
|
|
160
|
+
if (integrationCallbacks.length === 0) {
|
|
161
|
+
return undefined;
|
|
162
|
+
}
|
|
163
|
+
|
|
160
164
|
const mergedIntegrationCallback = mergeCallbacks(...integrationCallbacks);
|
|
161
165
|
|
|
162
166
|
return async (event: TelemetryEvent<KEY>) => {
|
|
@@ -164,6 +168,9 @@ export function createTelemetryDispatcher({
|
|
|
164
168
|
};
|
|
165
169
|
};
|
|
166
170
|
|
|
171
|
+
const onStepEnd = mergeTelemetryCallback('onStepEnd');
|
|
172
|
+
const onStepFinish = mergeTelemetryCallback('onStepFinish');
|
|
173
|
+
|
|
167
174
|
const executeLanguageModelCallWrappers = integrations
|
|
168
175
|
.map(integration => integration.executeLanguageModelCall?.bind(integration))
|
|
169
176
|
.filter(Boolean) as Array<
|
|
@@ -205,10 +212,10 @@ export function createTelemetryDispatcher({
|
|
|
205
212
|
// deprecated `onStepFinish` callback so integrations that still implement
|
|
206
213
|
// only `onStepFinish` keep receiving step-end events during the deprecation
|
|
207
214
|
// window.
|
|
208
|
-
onStepEnd:
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
215
|
+
onStepEnd:
|
|
216
|
+
onStepEnd == null && onStepFinish == null
|
|
217
|
+
? undefined
|
|
218
|
+
: mergeCallbacks(onStepEnd, onStepFinish),
|
|
212
219
|
onObjectStepStart: mergeTelemetryCallback('onObjectStepStart'),
|
|
213
220
|
onObjectStepEnd: mergeTelemetryCallback('onObjectStepEnd'),
|
|
214
221
|
onEmbedStart: mergeTelemetryCallback('onEmbedStart'),
|
|
@@ -227,6 +234,12 @@ export function createTelemetryDispatcher({
|
|
|
227
234
|
experimental_onEvaluateEnd: mergeTelemetryCallback(
|
|
228
235
|
'experimental_onEvaluateEnd',
|
|
229
236
|
),
|
|
237
|
+
experimental_onStreamTranscriptionStart: mergeTelemetryCallback(
|
|
238
|
+
'experimental_onStreamTranscriptionStart',
|
|
239
|
+
),
|
|
240
|
+
experimental_onStreamTranscriptionEnd: mergeTelemetryCallback(
|
|
241
|
+
'experimental_onStreamTranscriptionEnd',
|
|
242
|
+
),
|
|
230
243
|
onEnd: mergeTelemetryCallback('onEnd'),
|
|
231
244
|
onAbort: mergeTelemetryCallback('onAbort'),
|
|
232
245
|
onError: mergeTelemetryCallback('onError'),
|