@ai-sdk/groq 3.0.14 → 3.0.15

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,110 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { groqProviderOptions, GroqProviderOptions } from './groq-chat-options';
3
-
4
- describe('groqProviderOptions', () => {
5
- describe('reasoningEffort', () => {
6
- it('accepts valid reasoningEffort values', () => {
7
- const validValues = ['none', 'default', 'low', 'medium', 'high'] as const;
8
-
9
- validValues.forEach(value => {
10
- const result = groqProviderOptions.safeParse({
11
- reasoningEffort: value,
12
- });
13
- expect(result.success).toBe(true);
14
- expect(result.data?.reasoningEffort).toBe(value);
15
- });
16
- });
17
-
18
- it('rejects invalid reasoningEffort values', () => {
19
- const invalidValues = [
20
- 'invalid',
21
- 'high-effort',
22
- 'minimal',
23
- 'maximum',
24
- '',
25
- ];
26
-
27
- invalidValues.forEach(value => {
28
- const result = groqProviderOptions.safeParse({
29
- reasoningEffort: value,
30
- });
31
- expect(result.success).toBe(false);
32
- });
33
- });
34
-
35
- it('allows reasoningEffort to be undefined', () => {
36
- const result = groqProviderOptions.safeParse({});
37
- expect(result.success).toBe(true);
38
- expect(result.data?.reasoningEffort).toBeUndefined();
39
- });
40
-
41
- it('allows reasoningEffort to be omitted explicitly', () => {
42
- const result = groqProviderOptions.safeParse({
43
- reasoningEffort: undefined,
44
- });
45
- expect(result.success).toBe(true);
46
- expect(result.data?.reasoningEffort).toBeUndefined();
47
- });
48
- });
49
-
50
- describe('combined options with reasoningEffort', () => {
51
- it('accepts reasoningEffort with other valid options', () => {
52
- const result = groqProviderOptions.safeParse({
53
- reasoningEffort: 'high',
54
- parallelToolCalls: true,
55
- user: 'test-user',
56
- structuredOutputs: false,
57
- serviceTier: 'flex',
58
- });
59
-
60
- expect(result.success).toBe(true);
61
- expect(result.data?.reasoningEffort).toBe('high');
62
- expect(result.data?.parallelToolCalls).toBe(true);
63
- expect(result.data?.user).toBe('test-user');
64
- });
65
-
66
- it('rejects when reasoningEffort is invalid among valid options', () => {
67
- const result = groqProviderOptions.safeParse({
68
- reasoningEffort: 'ultra-high',
69
- parallelToolCalls: true,
70
- user: 'test-user',
71
- });
72
-
73
- expect(result.success).toBe(false);
74
- });
75
- });
76
-
77
- describe('all reasoningEffort enum variants', () => {
78
- it('validates all reasoningEffort variants individually', () => {
79
- const variants: Array<'none' | 'default' | 'low' | 'medium' | 'high'> = [
80
- 'none',
81
- 'default',
82
- 'low',
83
- 'medium',
84
- 'high',
85
- ];
86
-
87
- variants.forEach(variant => {
88
- const result = groqProviderOptions.safeParse({
89
- reasoningEffort: variant,
90
- });
91
- expect(result.success).toBe(true);
92
- if (result.success) {
93
- expect(result.data.reasoningEffort).toBe(variant);
94
- }
95
- });
96
- });
97
- });
98
-
99
- describe('type inference', () => {
100
- it('infers GroqProviderOptions type correctly', () => {
101
- const options: GroqProviderOptions = {
102
- reasoningEffort: 'medium',
103
- parallelToolCalls: false,
104
- };
105
-
106
- expect(options.reasoningEffort).toBe('medium');
107
- expect(options.parallelToolCalls).toBe(false);
108
- });
109
- });
110
- });
@@ -1,272 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { prepareTools } from './groq-prepare-tools';
3
-
4
- describe('prepareTools', () => {
5
- it('should return undefined tools and toolChoice when tools are null', () => {
6
- const result = prepareTools({
7
- tools: undefined,
8
- modelId: 'gemma2-9b-it',
9
- });
10
-
11
- expect(result).toEqual({
12
- tools: undefined,
13
- toolChoice: undefined,
14
- toolWarnings: [],
15
- });
16
- });
17
-
18
- it('should return undefined tools and toolChoice when tools are empty', () => {
19
- const result = prepareTools({
20
- tools: [],
21
- modelId: 'gemma2-9b-it',
22
- });
23
-
24
- expect(result).toEqual({
25
- tools: undefined,
26
- toolChoice: undefined,
27
- toolWarnings: [],
28
- });
29
- });
30
-
31
- it('should correctly prepare function tools', () => {
32
- const result = prepareTools({
33
- tools: [
34
- {
35
- type: 'function',
36
- name: 'testFunction',
37
- description: 'A test function',
38
- inputSchema: { type: 'object', properties: {} },
39
- },
40
- ],
41
- modelId: 'gemma2-9b-it',
42
- });
43
-
44
- expect(result.tools).toEqual([
45
- {
46
- type: 'function',
47
- function: {
48
- name: 'testFunction',
49
- description: 'A test function',
50
- parameters: { type: 'object', properties: {} },
51
- },
52
- },
53
- ]);
54
- expect(result.toolChoice).toBeUndefined();
55
- expect(result.toolWarnings).toEqual([]);
56
- });
57
-
58
- it('should add warnings for unsupported provider-defined tools', () => {
59
- const result = prepareTools({
60
- tools: [
61
- {
62
- type: 'provider',
63
- id: 'some.unsupported_tool',
64
- name: 'unsupported_tool',
65
- args: {},
66
- },
67
- ],
68
- modelId: 'gemma2-9b-it',
69
- });
70
-
71
- expect(result.tools).toEqual([]);
72
- expect(result.toolChoice).toBeUndefined();
73
- expect(result.toolWarnings).toMatchInlineSnapshot(`
74
- [
75
- {
76
- "feature": "provider-defined tool some.unsupported_tool",
77
- "type": "unsupported",
78
- },
79
- ]
80
- `);
81
- });
82
-
83
- it('should handle tool choice "auto"', () => {
84
- const result = prepareTools({
85
- tools: [
86
- {
87
- type: 'function',
88
- name: 'testFunction',
89
- description: 'Test',
90
- inputSchema: {},
91
- },
92
- ],
93
- toolChoice: { type: 'auto' },
94
- modelId: 'gemma2-9b-it',
95
- });
96
- expect(result.toolChoice).toEqual('auto');
97
- });
98
-
99
- it('should handle tool choice "required"', () => {
100
- const result = prepareTools({
101
- tools: [
102
- {
103
- type: 'function',
104
- name: 'testFunction',
105
- description: 'Test',
106
- inputSchema: {},
107
- },
108
- ],
109
- toolChoice: { type: 'required' },
110
- modelId: 'gemma2-9b-it',
111
- });
112
- expect(result.toolChoice).toEqual('required');
113
- });
114
-
115
- it('should handle tool choice "none"', () => {
116
- const result = prepareTools({
117
- tools: [
118
- {
119
- type: 'function',
120
- name: 'testFunction',
121
- description: 'Test',
122
- inputSchema: {},
123
- },
124
- ],
125
- toolChoice: { type: 'none' },
126
- modelId: 'gemma2-9b-it',
127
- });
128
- expect(result.toolChoice).toEqual('none');
129
- });
130
-
131
- it('should handle tool choice "tool"', () => {
132
- const result = prepareTools({
133
- tools: [
134
- {
135
- type: 'function',
136
- name: 'testFunction',
137
- description: 'Test',
138
- inputSchema: {},
139
- },
140
- ],
141
- toolChoice: { type: 'tool', toolName: 'testFunction' },
142
- modelId: 'gemma2-9b-it',
143
- });
144
- expect(result.toolChoice).toEqual({
145
- type: 'function',
146
- function: { name: 'testFunction' },
147
- });
148
- });
149
-
150
- describe('browser search tool', () => {
151
- it('should handle browser search tool with supported model', () => {
152
- const result = prepareTools({
153
- tools: [
154
- {
155
- type: 'provider',
156
- id: 'groq.browser_search',
157
- name: 'browser_search',
158
- args: {},
159
- },
160
- ],
161
- modelId: 'openai/gpt-oss-120b', // Supported model
162
- });
163
-
164
- expect(result.tools).toEqual([
165
- {
166
- type: 'browser_search',
167
- },
168
- ]);
169
- expect(result.toolWarnings).toEqual([]);
170
- });
171
-
172
- it('should warn when browser search is used with unsupported model', () => {
173
- const result = prepareTools({
174
- tools: [
175
- {
176
- type: 'provider',
177
- id: 'groq.browser_search',
178
- name: 'browser_search',
179
- args: {},
180
- },
181
- ],
182
- modelId: 'gemma2-9b-it', // Unsupported model
183
- });
184
-
185
- expect(result.tools).toEqual([]);
186
- expect(result.toolWarnings).toMatchInlineSnapshot(`
187
- [
188
- {
189
- "details": "Browser search is only supported on the following models: openai/gpt-oss-20b, openai/gpt-oss-120b. Current model: gemma2-9b-it",
190
- "feature": "provider-defined tool groq.browser_search",
191
- "type": "unsupported",
192
- },
193
- ]
194
- `);
195
- });
196
-
197
- it('should handle mixed tools with model validation', () => {
198
- const result = prepareTools({
199
- tools: [
200
- {
201
- type: 'function',
202
- name: 'test-tool',
203
- description: 'A test tool',
204
- inputSchema: { type: 'object', properties: {} },
205
- },
206
- {
207
- type: 'provider',
208
- id: 'groq.browser_search',
209
- name: 'browser_search',
210
- args: {},
211
- },
212
- ],
213
- modelId: 'openai/gpt-oss-20b', // Supported model
214
- });
215
-
216
- expect(result.tools).toEqual([
217
- {
218
- type: 'function',
219
- function: {
220
- name: 'test-tool',
221
- description: 'A test tool',
222
- parameters: { type: 'object', properties: {} },
223
- },
224
- },
225
- {
226
- type: 'browser_search',
227
- },
228
- ]);
229
- expect(result.toolWarnings).toEqual([]);
230
- });
231
-
232
- it('should validate all browser search supported models', () => {
233
- const supportedModels = ['openai/gpt-oss-20b', 'openai/gpt-oss-120b'];
234
-
235
- supportedModels.forEach(modelId => {
236
- const result = prepareTools({
237
- tools: [
238
- {
239
- type: 'provider',
240
- id: 'groq.browser_search',
241
- name: 'browser_search',
242
- args: {},
243
- },
244
- ],
245
- modelId: modelId as any,
246
- });
247
-
248
- expect(result.tools).toEqual([{ type: 'browser_search' }]);
249
- expect(result.toolWarnings).toEqual([]);
250
- });
251
- });
252
-
253
- it('should handle browser search with tool choice', () => {
254
- const result = prepareTools({
255
- tools: [
256
- {
257
- type: 'provider',
258
- id: 'groq.browser_search',
259
- name: 'browser_search',
260
- args: {},
261
- },
262
- ],
263
- toolChoice: { type: 'required' },
264
- modelId: 'openai/gpt-oss-120b',
265
- });
266
-
267
- expect(result.tools).toEqual([{ type: 'browser_search' }]);
268
- expect(result.toolChoice).toEqual('required');
269
- expect(result.toolWarnings).toEqual([]);
270
- });
271
- });
272
- });
@@ -1,185 +0,0 @@
1
- import { createTestServer } from '@ai-sdk/test-server/with-vitest';
2
- import { GroqTranscriptionModel } from './groq-transcription-model';
3
- import { createGroq } from './groq-provider';
4
- import { readFile } from 'node:fs/promises';
5
- import path from 'node:path';
6
- import { describe, it, expect, vi } from 'vitest';
7
-
8
- vi.mock('./version', () => ({
9
- VERSION: '0.0.0-test',
10
- }));
11
-
12
- const audioData = await readFile(path.join(__dirname, 'transcript-test.mp3'));
13
- const provider = createGroq({ apiKey: 'test-api-key' });
14
- const model = provider.transcription('whisper-large-v3-turbo');
15
-
16
- const server = createTestServer({
17
- 'https://api.groq.com/openai/v1/audio/transcriptions': {},
18
- });
19
-
20
- describe('doGenerate', () => {
21
- function prepareJsonResponse({
22
- headers,
23
- }: {
24
- headers?: Record<string, string>;
25
- } = {}) {
26
- server.urls[
27
- 'https://api.groq.com/openai/v1/audio/transcriptions'
28
- ].response = {
29
- type: 'json-value',
30
- headers,
31
- body: {
32
- task: 'transcribe',
33
- language: 'English',
34
- duration: 2.5,
35
- text: 'Hello world!',
36
- segments: [
37
- {
38
- id: 0,
39
- seek: 0,
40
- start: 0,
41
- end: 2.48,
42
- text: 'Hello world!',
43
- tokens: [50365, 2425, 490, 264],
44
- temperature: 0,
45
- avg_logprob: -0.29010406,
46
- compression_ratio: 0.7777778,
47
- no_speech_prob: 0.032802984,
48
- },
49
- ],
50
- x_groq: { id: 'req_01jrh9nn61f24rydqq1r4b3yg5' },
51
- },
52
- };
53
- }
54
-
55
- it('should pass the model', async () => {
56
- prepareJsonResponse();
57
-
58
- await model.doGenerate({
59
- audio: audioData,
60
- mediaType: 'audio/wav',
61
- });
62
-
63
- expect(await server.calls[0].requestBodyMultipart).toMatchObject({
64
- model: 'whisper-large-v3-turbo',
65
- });
66
- });
67
-
68
- it('should pass headers', async () => {
69
- prepareJsonResponse();
70
-
71
- const provider = createGroq({
72
- apiKey: 'test-api-key',
73
- headers: {
74
- 'Custom-Provider-Header': 'provider-header-value',
75
- },
76
- });
77
-
78
- await provider.transcription('whisper-large-v3-turbo').doGenerate({
79
- audio: audioData,
80
- mediaType: 'audio/wav',
81
- headers: {
82
- 'Custom-Request-Header': 'request-header-value',
83
- },
84
- });
85
-
86
- expect(server.calls[0].requestHeaders).toMatchObject({
87
- authorization: 'Bearer test-api-key',
88
- 'content-type': expect.stringMatching(
89
- /^multipart\/form-data; boundary=----formdata-undici-\d+$/,
90
- ),
91
- 'custom-provider-header': 'provider-header-value',
92
- 'custom-request-header': 'request-header-value',
93
- });
94
- expect(server.calls[0].requestUserAgent).toContain(
95
- `ai-sdk/groq/0.0.0-test`,
96
- );
97
- });
98
-
99
- it('should extract the transcription text', async () => {
100
- prepareJsonResponse();
101
-
102
- const result = await model.doGenerate({
103
- audio: audioData,
104
- mediaType: 'audio/wav',
105
- });
106
-
107
- expect(result.text).toBe('Hello world!');
108
- });
109
-
110
- it('should include response data with timestamp, modelId and headers', async () => {
111
- prepareJsonResponse({
112
- headers: {
113
- 'x-request-id': 'test-request-id',
114
- 'x-ratelimit-remaining': '123',
115
- },
116
- });
117
-
118
- const testDate = new Date(0);
119
- const customModel = new GroqTranscriptionModel('whisper-large-v3-turbo', {
120
- provider: 'test-provider',
121
- url: () => 'https://api.groq.com/openai/v1/audio/transcriptions',
122
- headers: () => ({}),
123
- _internal: {
124
- currentDate: () => testDate,
125
- },
126
- });
127
-
128
- const result = await customModel.doGenerate({
129
- audio: audioData,
130
- mediaType: 'audio/wav',
131
- });
132
-
133
- expect(result.response).toMatchObject({
134
- timestamp: testDate,
135
- modelId: 'whisper-large-v3-turbo',
136
- headers: {
137
- 'content-type': 'application/json',
138
- 'x-request-id': 'test-request-id',
139
- 'x-ratelimit-remaining': '123',
140
- },
141
- });
142
- });
143
-
144
- it('should use real date when no custom date provider is specified', async () => {
145
- prepareJsonResponse();
146
-
147
- const testDate = new Date(0);
148
- const customModel = new GroqTranscriptionModel('whisper-large-v3-turbo', {
149
- provider: 'test-provider',
150
- url: () => 'https://api.groq.com/openai/v1/audio/transcriptions',
151
- headers: () => ({}),
152
- _internal: {
153
- currentDate: () => testDate,
154
- },
155
- });
156
-
157
- const result = await customModel.doGenerate({
158
- audio: audioData,
159
- mediaType: 'audio/wav',
160
- });
161
-
162
- expect(result.response.timestamp.getTime()).toEqual(testDate.getTime());
163
- expect(result.response.modelId).toBe('whisper-large-v3-turbo');
164
- });
165
-
166
- it('should correctly pass provider options when they are an array', async () => {
167
- prepareJsonResponse();
168
-
169
- await model.doGenerate({
170
- audio: audioData,
171
- mediaType: 'audio/wav',
172
- providerOptions: {
173
- groq: {
174
- timestampGranularities: ['segment'],
175
- responseFormat: 'verbose_json',
176
- },
177
- },
178
- });
179
-
180
- expect(await server.calls[0].requestBodyMultipart).toMatchObject({
181
- 'timestamp_granularities[]': 'segment',
182
- response_format: 'verbose_json',
183
- });
184
- });
185
- });