@ai-sdk/groq 3.0.12 → 3.0.13

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.
@@ -0,0 +1,2028 @@
1
+ import { LanguageModelV3Prompt } from '@ai-sdk/provider';
2
+ import { createTestServer } from '@ai-sdk/test-server/with-vitest';
3
+ import {
4
+ convertReadableStreamToArray,
5
+ isNodeVersion,
6
+ } from '@ai-sdk/provider-utils/test';
7
+ import { createGroq } from './groq-provider';
8
+ import { describe, it, expect, vi } from 'vitest';
9
+
10
+ vi.mock('./version', () => ({
11
+ VERSION: '0.0.0-test',
12
+ }));
13
+
14
+ const TEST_PROMPT: LanguageModelV3Prompt = [
15
+ { role: 'user', content: [{ type: 'text', text: 'Hello' }] },
16
+ ];
17
+
18
+ const provider = createGroq({ apiKey: 'test-api-key' });
19
+ const model = provider('gemma2-9b-it');
20
+
21
+ const server = createTestServer({
22
+ 'https://api.groq.com/openai/v1/chat/completions': {},
23
+ });
24
+
25
+ describe('doGenerate', () => {
26
+ function prepareJsonResponse({
27
+ content = '',
28
+ reasoning,
29
+ tool_calls,
30
+ function_call,
31
+ usage = {
32
+ prompt_tokens: 4,
33
+ total_tokens: 34,
34
+ completion_tokens: 30,
35
+ },
36
+ finish_reason = 'stop',
37
+ id = 'chatcmpl-95ZTZkhr0mHNKqerQfiwkuox3PHAd',
38
+ created = 1711115037,
39
+ model = 'gemma2-9b-it',
40
+ headers,
41
+ }: {
42
+ content?: string;
43
+ reasoning?: string;
44
+ tool_calls?: Array<{
45
+ id: string;
46
+ type: 'function';
47
+ function: {
48
+ name: string;
49
+ arguments: string;
50
+ };
51
+ }>;
52
+ function_call?: {
53
+ name: string;
54
+ arguments: string;
55
+ };
56
+ usage?: {
57
+ prompt_tokens?: number;
58
+ total_tokens?: number;
59
+ completion_tokens?: number;
60
+ prompt_tokens_details?: {
61
+ cached_tokens?: number;
62
+ };
63
+ completion_tokens_details?: {
64
+ reasoning_tokens?: number;
65
+ };
66
+ };
67
+ finish_reason?: string;
68
+ created?: number;
69
+ id?: string;
70
+ model?: string;
71
+ headers?: Record<string, string>;
72
+ } = {}) {
73
+ server.urls['https://api.groq.com/openai/v1/chat/completions'].response = {
74
+ type: 'json-value',
75
+ headers,
76
+ body: {
77
+ id,
78
+ object: 'chat.completion',
79
+ created,
80
+ model,
81
+ choices: [
82
+ {
83
+ index: 0,
84
+ message: {
85
+ role: 'assistant',
86
+ content,
87
+ reasoning,
88
+ tool_calls,
89
+ function_call,
90
+ },
91
+ finish_reason,
92
+ },
93
+ ],
94
+ usage,
95
+ system_fingerprint: 'fp_3bc1b5746c',
96
+ },
97
+ };
98
+ }
99
+
100
+ it('should extract text', async () => {
101
+ prepareJsonResponse({ content: 'Hello, World!' });
102
+
103
+ const { content } = await model.doGenerate({
104
+ prompt: TEST_PROMPT,
105
+ });
106
+
107
+ expect(content).toMatchInlineSnapshot(`
108
+ [
109
+ {
110
+ "text": "Hello, World!",
111
+ "type": "text",
112
+ },
113
+ ]
114
+ `);
115
+ });
116
+
117
+ it('should extract reasoning', async () => {
118
+ prepareJsonResponse({
119
+ reasoning: 'This is a test reasoning',
120
+ });
121
+
122
+ const { content } = await model.doGenerate({
123
+ prompt: TEST_PROMPT,
124
+ });
125
+
126
+ expect(content).toMatchInlineSnapshot(`
127
+ [
128
+ {
129
+ "text": "This is a test reasoning",
130
+ "type": "reasoning",
131
+ },
132
+ ]
133
+ `);
134
+ });
135
+
136
+ it('should extract usage', async () => {
137
+ prepareJsonResponse({
138
+ usage: { prompt_tokens: 20, total_tokens: 25, completion_tokens: 5 },
139
+ });
140
+
141
+ const { usage } = await model.doGenerate({
142
+ prompt: TEST_PROMPT,
143
+ });
144
+
145
+ expect(usage).toMatchInlineSnapshot(`
146
+ {
147
+ "inputTokens": {
148
+ "cacheRead": undefined,
149
+ "cacheWrite": undefined,
150
+ "noCache": 20,
151
+ "total": 20,
152
+ },
153
+ "outputTokens": {
154
+ "reasoning": undefined,
155
+ "text": 5,
156
+ "total": 5,
157
+ },
158
+ "raw": {
159
+ "completion_tokens": 5,
160
+ "prompt_tokens": 20,
161
+ "total_tokens": 25,
162
+ },
163
+ }
164
+ `);
165
+ });
166
+ it('should send additional response information', async () => {
167
+ prepareJsonResponse({
168
+ id: 'test-id',
169
+ created: 123,
170
+ model: 'test-model',
171
+ });
172
+
173
+ const { response } = await model.doGenerate({
174
+ prompt: TEST_PROMPT,
175
+ });
176
+
177
+ expect({
178
+ id: response?.id,
179
+ timestamp: response?.timestamp,
180
+ modelId: response?.modelId,
181
+ }).toStrictEqual({
182
+ id: 'test-id',
183
+ timestamp: new Date(123 * 1000),
184
+ modelId: 'test-model',
185
+ });
186
+ });
187
+
188
+ it('should support partial usage', async () => {
189
+ prepareJsonResponse({
190
+ usage: { prompt_tokens: 20, total_tokens: 20 },
191
+ });
192
+
193
+ const { usage } = await model.doGenerate({
194
+ prompt: TEST_PROMPT,
195
+ });
196
+
197
+ expect(usage).toMatchInlineSnapshot(`
198
+ {
199
+ "inputTokens": {
200
+ "cacheRead": undefined,
201
+ "cacheWrite": undefined,
202
+ "noCache": 20,
203
+ "total": 20,
204
+ },
205
+ "outputTokens": {
206
+ "reasoning": undefined,
207
+ "text": 0,
208
+ "total": 0,
209
+ },
210
+ "raw": {
211
+ "prompt_tokens": 20,
212
+ "total_tokens": 20,
213
+ },
214
+ }
215
+ `);
216
+ });
217
+
218
+ it('should extract cached input tokens', async () => {
219
+ prepareJsonResponse({
220
+ usage: {
221
+ prompt_tokens: 20,
222
+ total_tokens: 25,
223
+ completion_tokens: 5,
224
+ prompt_tokens_details: {
225
+ cached_tokens: 15,
226
+ },
227
+ },
228
+ });
229
+
230
+ const { usage } = await model.doGenerate({
231
+ prompt: TEST_PROMPT,
232
+ });
233
+
234
+ expect(usage).toMatchInlineSnapshot(`
235
+ {
236
+ "inputTokens": {
237
+ "cacheRead": undefined,
238
+ "cacheWrite": undefined,
239
+ "noCache": 20,
240
+ "total": 20,
241
+ },
242
+ "outputTokens": {
243
+ "reasoning": undefined,
244
+ "text": 5,
245
+ "total": 5,
246
+ },
247
+ "raw": {
248
+ "completion_tokens": 5,
249
+ "prompt_tokens": 20,
250
+ "prompt_tokens_details": {
251
+ "cached_tokens": 15,
252
+ },
253
+ "total_tokens": 25,
254
+ },
255
+ }
256
+ `);
257
+ });
258
+
259
+ it('should extract reasoning tokens from completion_tokens_details', async () => {
260
+ prepareJsonResponse({
261
+ usage: {
262
+ prompt_tokens: 79,
263
+ total_tokens: 119,
264
+ completion_tokens: 40,
265
+ completion_tokens_details: {
266
+ reasoning_tokens: 21,
267
+ },
268
+ },
269
+ });
270
+
271
+ const { usage } = await model.doGenerate({
272
+ prompt: TEST_PROMPT,
273
+ });
274
+
275
+ expect(usage).toMatchInlineSnapshot(`
276
+ {
277
+ "inputTokens": {
278
+ "cacheRead": undefined,
279
+ "cacheWrite": undefined,
280
+ "noCache": 79,
281
+ "total": 79,
282
+ },
283
+ "outputTokens": {
284
+ "reasoning": 21,
285
+ "text": 19,
286
+ "total": 40,
287
+ },
288
+ "raw": {
289
+ "completion_tokens": 40,
290
+ "completion_tokens_details": {
291
+ "reasoning_tokens": 21,
292
+ },
293
+ "prompt_tokens": 79,
294
+ "total_tokens": 119,
295
+ },
296
+ }
297
+ `);
298
+ });
299
+
300
+ it('should extract finish reason', async () => {
301
+ prepareJsonResponse({
302
+ finish_reason: 'stop',
303
+ });
304
+
305
+ const response = await model.doGenerate({
306
+ prompt: TEST_PROMPT,
307
+ });
308
+
309
+ expect(response.finishReason).toMatchInlineSnapshot(`
310
+ {
311
+ "raw": "stop",
312
+ "unified": "stop",
313
+ }
314
+ `);
315
+ });
316
+
317
+ it('should support unknown finish reason', async () => {
318
+ prepareJsonResponse({
319
+ finish_reason: 'eos',
320
+ });
321
+
322
+ const response = await model.doGenerate({
323
+ prompt: TEST_PROMPT,
324
+ });
325
+
326
+ expect(response.finishReason).toMatchInlineSnapshot(`
327
+ {
328
+ "raw": "eos",
329
+ "unified": "other",
330
+ }
331
+ `);
332
+ });
333
+
334
+ it('should expose the raw response headers', async () => {
335
+ prepareJsonResponse({
336
+ headers: {
337
+ 'test-header': 'test-value',
338
+ },
339
+ });
340
+
341
+ const { response } = await model.doGenerate({
342
+ prompt: TEST_PROMPT,
343
+ });
344
+
345
+ expect(response?.headers).toStrictEqual({
346
+ // default headers:
347
+ 'content-length': '315',
348
+ 'content-type': 'application/json',
349
+
350
+ // custom header
351
+ 'test-header': 'test-value',
352
+ });
353
+ });
354
+
355
+ it('should pass the model and the messages', async () => {
356
+ prepareJsonResponse({ content: '' });
357
+
358
+ await model.doGenerate({
359
+ prompt: TEST_PROMPT,
360
+ });
361
+
362
+ expect(await server.calls[0].requestBodyJson).toStrictEqual({
363
+ model: 'gemma2-9b-it',
364
+ messages: [{ role: 'user', content: 'Hello' }],
365
+ });
366
+ });
367
+
368
+ it('should pass provider options', async () => {
369
+ prepareJsonResponse();
370
+
371
+ await provider('gemma2-9b-it').doGenerate({
372
+ prompt: TEST_PROMPT,
373
+ providerOptions: {
374
+ groq: {
375
+ reasoningFormat: 'hidden',
376
+ user: 'test-user-id',
377
+ parallelToolCalls: false,
378
+ },
379
+ },
380
+ });
381
+
382
+ expect(await server.calls[0].requestBodyJson).toStrictEqual({
383
+ model: 'gemma2-9b-it',
384
+ messages: [{ role: 'user', content: 'Hello' }],
385
+ parallel_tool_calls: false,
386
+ user: 'test-user-id',
387
+ reasoning_format: 'hidden',
388
+ });
389
+ });
390
+
391
+ it('should pass serviceTier provider option', async () => {
392
+ prepareJsonResponse();
393
+
394
+ await provider('gemma2-9b-it').doGenerate({
395
+ prompt: TEST_PROMPT,
396
+ providerOptions: {
397
+ groq: {
398
+ serviceTier: 'flex',
399
+ },
400
+ },
401
+ });
402
+
403
+ expect(await server.calls[0].requestBodyJson).toStrictEqual({
404
+ model: 'gemma2-9b-it',
405
+ messages: [{ role: 'user', content: 'Hello' }],
406
+ service_tier: 'flex',
407
+ });
408
+ });
409
+
410
+ it('should pass tools and toolChoice', async () => {
411
+ prepareJsonResponse({ content: '' });
412
+
413
+ await model.doGenerate({
414
+ tools: [
415
+ {
416
+ type: 'function',
417
+ name: 'test-tool',
418
+ inputSchema: {
419
+ type: 'object',
420
+ properties: { value: { type: 'string' } },
421
+ required: ['value'],
422
+ additionalProperties: false,
423
+ $schema: 'http://json-schema.org/draft-07/schema#',
424
+ },
425
+ },
426
+ ],
427
+ toolChoice: {
428
+ type: 'tool',
429
+ toolName: 'test-tool',
430
+ },
431
+ prompt: TEST_PROMPT,
432
+ });
433
+
434
+ expect(await server.calls[0].requestBodyJson).toStrictEqual({
435
+ model: 'gemma2-9b-it',
436
+ messages: [{ role: 'user', content: 'Hello' }],
437
+ tools: [
438
+ {
439
+ type: 'function',
440
+ function: {
441
+ name: 'test-tool',
442
+ parameters: {
443
+ type: 'object',
444
+ properties: { value: { type: 'string' } },
445
+ required: ['value'],
446
+ additionalProperties: false,
447
+ $schema: 'http://json-schema.org/draft-07/schema#',
448
+ },
449
+ },
450
+ },
451
+ ],
452
+ tool_choice: {
453
+ type: 'function',
454
+ function: { name: 'test-tool' },
455
+ },
456
+ });
457
+ });
458
+
459
+ it('should pass headers', async () => {
460
+ prepareJsonResponse({ content: '' });
461
+
462
+ const provider = createGroq({
463
+ apiKey: 'test-api-key',
464
+ headers: {
465
+ 'Custom-Provider-Header': 'provider-header-value',
466
+ },
467
+ });
468
+
469
+ await provider('gemma2-9b-it').doGenerate({
470
+ prompt: TEST_PROMPT,
471
+ headers: {
472
+ 'Custom-Request-Header': 'request-header-value',
473
+ },
474
+ });
475
+
476
+ expect(server.calls[0].requestHeaders).toStrictEqual({
477
+ authorization: 'Bearer test-api-key',
478
+ 'content-type': 'application/json',
479
+ 'custom-provider-header': 'provider-header-value',
480
+ 'custom-request-header': 'request-header-value',
481
+ });
482
+ expect(server.calls[0].requestUserAgent).toContain(
483
+ `ai-sdk/groq/0.0.0-test`,
484
+ );
485
+ });
486
+
487
+ it('should parse tool results', async () => {
488
+ prepareJsonResponse({
489
+ tool_calls: [
490
+ {
491
+ id: 'call_O17Uplv4lJvD6DVdIvFFeRMw',
492
+ type: 'function',
493
+ function: {
494
+ name: 'test-tool',
495
+ arguments: '{"value":"Spark"}',
496
+ },
497
+ },
498
+ ],
499
+ });
500
+
501
+ const result = await model.doGenerate({
502
+ tools: [
503
+ {
504
+ type: 'function',
505
+ name: 'test-tool',
506
+ inputSchema: {
507
+ type: 'object',
508
+ properties: { value: { type: 'string' } },
509
+ required: ['value'],
510
+ additionalProperties: false,
511
+ $schema: 'http://json-schema.org/draft-07/schema#',
512
+ },
513
+ },
514
+ ],
515
+ toolChoice: {
516
+ type: 'tool',
517
+ toolName: 'test-tool',
518
+ },
519
+ prompt: TEST_PROMPT,
520
+ });
521
+
522
+ expect(result.content).toMatchInlineSnapshot(`
523
+ [
524
+ {
525
+ "input": "{"value":"Spark"}",
526
+ "toolCallId": "call_O17Uplv4lJvD6DVdIvFFeRMw",
527
+ "toolName": "test-tool",
528
+ "type": "tool-call",
529
+ },
530
+ ]
531
+ `);
532
+ });
533
+
534
+ it('should pass response format information as json_schema when structuredOutputs enabled by default', async () => {
535
+ prepareJsonResponse({ content: '{"value":"Spark"}' });
536
+
537
+ const model = provider('gemma2-9b-it');
538
+
539
+ await model.doGenerate({
540
+ responseFormat: {
541
+ type: 'json',
542
+ name: 'test-name',
543
+ description: 'test description',
544
+ schema: {
545
+ type: 'object',
546
+ properties: { value: { type: 'string' } },
547
+ required: ['value'],
548
+ additionalProperties: false,
549
+ $schema: 'http://json-schema.org/draft-07/schema#',
550
+ },
551
+ },
552
+ prompt: TEST_PROMPT,
553
+ });
554
+
555
+ expect(await server.calls[0].requestBodyJson).toStrictEqual({
556
+ model: 'gemma2-9b-it',
557
+ messages: [{ role: 'user', content: 'Hello' }],
558
+ response_format: {
559
+ type: 'json_schema',
560
+ json_schema: {
561
+ strict: true,
562
+ name: 'test-name',
563
+ description: 'test description',
564
+ schema: {
565
+ type: 'object',
566
+ properties: { value: { type: 'string' } },
567
+ required: ['value'],
568
+ additionalProperties: false,
569
+ $schema: 'http://json-schema.org/draft-07/schema#',
570
+ },
571
+ },
572
+ },
573
+ });
574
+ });
575
+
576
+ it('should pass response format information as json_object when structuredOutputs explicitly disabled', async () => {
577
+ prepareJsonResponse({ content: '{"value":"Spark"}' });
578
+
579
+ const model = provider('gemma2-9b-it');
580
+
581
+ const { warnings } = await model.doGenerate({
582
+ providerOptions: {
583
+ groq: {
584
+ structuredOutputs: false,
585
+ },
586
+ },
587
+ responseFormat: {
588
+ type: 'json',
589
+ name: 'test-name',
590
+ description: 'test description',
591
+ schema: {
592
+ type: 'object',
593
+ properties: { value: { type: 'string' } },
594
+ required: ['value'],
595
+ additionalProperties: false,
596
+ $schema: 'http://json-schema.org/draft-07/schema#',
597
+ },
598
+ },
599
+ prompt: TEST_PROMPT,
600
+ });
601
+
602
+ expect(await server.calls[0].requestBodyJson).toStrictEqual({
603
+ model: 'gemma2-9b-it',
604
+ messages: [{ role: 'user', content: 'Hello' }],
605
+ response_format: {
606
+ type: 'json_object',
607
+ },
608
+ });
609
+
610
+ expect(warnings).toMatchInlineSnapshot(`
611
+ [
612
+ {
613
+ "details": "JSON response format schema is only supported with structuredOutputs",
614
+ "feature": "responseFormat",
615
+ "type": "unsupported",
616
+ },
617
+ ]
618
+ `);
619
+ });
620
+
621
+ it('should use json_schema format when structuredOutputs explicitly enabled', async () => {
622
+ prepareJsonResponse({ content: '{"value":"Spark"}' });
623
+
624
+ const model = provider('gemma2-9b-it');
625
+
626
+ await model.doGenerate({
627
+ providerOptions: {
628
+ groq: {
629
+ structuredOutputs: true,
630
+ },
631
+ },
632
+ responseFormat: {
633
+ type: 'json',
634
+ name: 'test-name',
635
+ description: 'test description',
636
+ schema: {
637
+ type: 'object',
638
+ properties: { value: { type: 'string' } },
639
+ required: ['value'],
640
+ additionalProperties: false,
641
+ $schema: 'http://json-schema.org/draft-07/schema#',
642
+ },
643
+ },
644
+ prompt: TEST_PROMPT,
645
+ });
646
+
647
+ expect(await server.calls[0].requestBodyJson).toStrictEqual({
648
+ model: 'gemma2-9b-it',
649
+ messages: [{ role: 'user', content: 'Hello' }],
650
+ response_format: {
651
+ type: 'json_schema',
652
+ json_schema: {
653
+ strict: true,
654
+ name: 'test-name',
655
+ description: 'test description',
656
+ schema: {
657
+ type: 'object',
658
+ properties: { value: { type: 'string' } },
659
+ required: ['value'],
660
+ additionalProperties: false,
661
+ $schema: 'http://json-schema.org/draft-07/schema#',
662
+ },
663
+ },
664
+ },
665
+ });
666
+ });
667
+
668
+ it('should allow explicit structuredOutputs override', async () => {
669
+ prepareJsonResponse({ content: '{"value":"Spark"}' });
670
+
671
+ const model = provider('gemma2-9b-it');
672
+
673
+ await model.doGenerate({
674
+ providerOptions: {
675
+ groq: {
676
+ structuredOutputs: true,
677
+ },
678
+ },
679
+ responseFormat: {
680
+ type: 'json',
681
+ schema: {
682
+ type: 'object',
683
+ properties: { value: { type: 'string' } },
684
+ required: ['value'],
685
+ additionalProperties: false,
686
+ $schema: 'http://json-schema.org/draft-07/schema#',
687
+ },
688
+ },
689
+ prompt: TEST_PROMPT,
690
+ });
691
+
692
+ expect(await server.calls[0].requestBodyJson).toStrictEqual({
693
+ model: 'gemma2-9b-it',
694
+ messages: [{ role: 'user', content: 'Hello' }],
695
+ response_format: {
696
+ type: 'json_schema',
697
+ json_schema: {
698
+ strict: true,
699
+ name: 'response',
700
+ schema: {
701
+ type: 'object',
702
+ properties: { value: { type: 'string' } },
703
+ required: ['value'],
704
+ additionalProperties: false,
705
+ $schema: 'http://json-schema.org/draft-07/schema#',
706
+ },
707
+ },
708
+ },
709
+ });
710
+ });
711
+
712
+ it('should send strict: false when strictJsonSchema is explicitly disabled', async () => {
713
+ prepareJsonResponse({ content: '{"value":"Spark"}' });
714
+
715
+ const model = provider('gemma2-9b-it');
716
+
717
+ await model.doGenerate({
718
+ providerOptions: {
719
+ groq: {
720
+ strictJsonSchema: false,
721
+ },
722
+ },
723
+ responseFormat: {
724
+ type: 'json',
725
+ name: 'test-name',
726
+ description: 'test description',
727
+ schema: {
728
+ type: 'object',
729
+ properties: { value: { type: 'string' } },
730
+ required: ['value'],
731
+ additionalProperties: false,
732
+ $schema: 'http://json-schema.org/draft-07/schema#',
733
+ },
734
+ },
735
+ prompt: TEST_PROMPT,
736
+ });
737
+
738
+ expect(await server.calls[0].requestBodyJson).toStrictEqual({
739
+ model: 'gemma2-9b-it',
740
+ messages: [{ role: 'user', content: 'Hello' }],
741
+ response_format: {
742
+ type: 'json_schema',
743
+ json_schema: {
744
+ strict: false,
745
+ name: 'test-name',
746
+ description: 'test description',
747
+ schema: {
748
+ type: 'object',
749
+ properties: { value: { type: 'string' } },
750
+ required: ['value'],
751
+ additionalProperties: false,
752
+ $schema: 'http://json-schema.org/draft-07/schema#',
753
+ },
754
+ },
755
+ },
756
+ });
757
+ });
758
+
759
+ it('should handle structured outputs with Kimi K2 model', async () => {
760
+ prepareJsonResponse({
761
+ content:
762
+ '{"recipe":{"name":"Spaghetti Aglio e Olio","ingredients":["spaghetti","garlic","olive oil","parmesan"],"instructions":["Boil pasta","Sauté garlic","Combine"]}}',
763
+ });
764
+
765
+ const kimiModel = provider('moonshotai/kimi-k2-instruct-0905');
766
+
767
+ const result = await kimiModel.doGenerate({
768
+ providerOptions: {
769
+ groq: {
770
+ structuredOutputs: true,
771
+ },
772
+ },
773
+ responseFormat: {
774
+ type: 'json',
775
+ name: 'recipe_response',
776
+ description: 'A recipe with ingredients and instructions',
777
+ schema: {
778
+ type: 'object',
779
+ properties: {
780
+ recipe: {
781
+ type: 'object',
782
+ properties: {
783
+ name: { type: 'string' },
784
+ ingredients: { type: 'array', items: { type: 'string' } },
785
+ instructions: { type: 'array', items: { type: 'string' } },
786
+ },
787
+ required: ['name', 'ingredients', 'instructions'],
788
+ },
789
+ },
790
+ required: ['recipe'],
791
+ additionalProperties: false,
792
+ $schema: 'http://json-schema.org/draft-07/schema#',
793
+ },
794
+ },
795
+ prompt: [
796
+ {
797
+ role: 'user',
798
+ content: [{ type: 'text', text: 'Generate a simple pasta recipe' }],
799
+ },
800
+ ],
801
+ });
802
+
803
+ expect(await server.calls[0].requestBodyJson).toMatchInlineSnapshot(`
804
+ {
805
+ "messages": [
806
+ {
807
+ "content": "Generate a simple pasta recipe",
808
+ "role": "user",
809
+ },
810
+ ],
811
+ "model": "moonshotai/kimi-k2-instruct-0905",
812
+ "response_format": {
813
+ "json_schema": {
814
+ "description": "A recipe with ingredients and instructions",
815
+ "name": "recipe_response",
816
+ "schema": {
817
+ "$schema": "http://json-schema.org/draft-07/schema#",
818
+ "additionalProperties": false,
819
+ "properties": {
820
+ "recipe": {
821
+ "properties": {
822
+ "ingredients": {
823
+ "items": {
824
+ "type": "string",
825
+ },
826
+ "type": "array",
827
+ },
828
+ "instructions": {
829
+ "items": {
830
+ "type": "string",
831
+ },
832
+ "type": "array",
833
+ },
834
+ "name": {
835
+ "type": "string",
836
+ },
837
+ },
838
+ "required": [
839
+ "name",
840
+ "ingredients",
841
+ "instructions",
842
+ ],
843
+ "type": "object",
844
+ },
845
+ },
846
+ "required": [
847
+ "recipe",
848
+ ],
849
+ "type": "object",
850
+ },
851
+ "strict": true,
852
+ },
853
+ "type": "json_schema",
854
+ },
855
+ }
856
+ `);
857
+
858
+ expect(result.content).toMatchInlineSnapshot(`
859
+ [
860
+ {
861
+ "text": "{"recipe":{"name":"Spaghetti Aglio e Olio","ingredients":["spaghetti","garlic","olive oil","parmesan"],"instructions":["Boil pasta","Sauté garlic","Combine"]}}",
862
+ "type": "text",
863
+ },
864
+ ]
865
+ `);
866
+ });
867
+
868
+ it('should include warnings when structured outputs explicitly disabled but schema provided', async () => {
869
+ prepareJsonResponse({ content: '{"value":"test"}' });
870
+
871
+ const { warnings } = await model.doGenerate({
872
+ providerOptions: {
873
+ groq: {
874
+ structuredOutputs: false,
875
+ },
876
+ },
877
+ responseFormat: {
878
+ type: 'json',
879
+ schema: {
880
+ type: 'object',
881
+ properties: { value: { type: 'string' } },
882
+ required: ['value'],
883
+ additionalProperties: false,
884
+ $schema: 'http://json-schema.org/draft-07/schema#',
885
+ },
886
+ },
887
+ prompt: TEST_PROMPT,
888
+ });
889
+
890
+ expect(warnings).toMatchInlineSnapshot(`
891
+ [
892
+ {
893
+ "details": "JSON response format schema is only supported with structuredOutputs",
894
+ "feature": "responseFormat",
895
+ "type": "unsupported",
896
+ },
897
+ ]
898
+ `);
899
+ });
900
+
901
+ it('should send request body', async () => {
902
+ prepareJsonResponse({ content: '' });
903
+
904
+ const { request } = await model.doGenerate({
905
+ prompt: TEST_PROMPT,
906
+ });
907
+
908
+ expect(request).toStrictEqual({
909
+ body: '{"model":"gemma2-9b-it","messages":[{"role":"user","content":"Hello"}]}',
910
+ });
911
+ });
912
+ });
913
+
914
+ describe('doStream', () => {
915
+ function prepareStreamResponse({
916
+ content = [],
917
+ finish_reason = 'stop',
918
+ headers,
919
+ }: {
920
+ content?: string[];
921
+ finish_reason?: string;
922
+ headers?: Record<string, string>;
923
+ }) {
924
+ server.urls['https://api.groq.com/openai/v1/chat/completions'].response = {
925
+ type: 'stream-chunks',
926
+ headers,
927
+ chunks: [
928
+ `data: {"id":"chatcmpl-e7f8e220-656c-4455-a132-dacfc1370798","object":"chat.completion.chunk","created":1702657020,"model":"gemma2-9b-it",` +
929
+ `"system_fingerprint":null,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null}]}\n\n`,
930
+ ...content.map(text => {
931
+ return (
932
+ `data: {"id":"chatcmpl-e7f8e220-656c-4455-a132-dacfc1370798","object":"chat.completion.chunk","created":1702657020,"model":"gemma2-9b-it",` +
933
+ `"system_fingerprint":null,"choices":[{"index":1,"delta":{"content":"${text}"},"finish_reason":null}]}\n\n`
934
+ );
935
+ }),
936
+ `data: {"id":"chatcmpl-e7f8e220-656c-4455-a132-dacfc1370798","object":"chat.completion.chunk","created":1702657020,"model":"gemma2-9b-it",` +
937
+ `"system_fingerprint":null,"choices":[{"index":0,"delta":{},"finish_reason":"${finish_reason}"}]}\n\n`,
938
+ `data: {"id":"chatcmpl-e7f8e220-656c-4455-a132-dacfc1370798","object":"chat.completion.chunk","created":1729171479,"model":"gemma2-9b-it",` +
939
+ `"system_fingerprint":"fp_10c08bf97d","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"${finish_reason}"}],` +
940
+ `"x_groq":{"id":"req_01jadadp0femyae9kav1gpkhe8","usage":{"queue_time":0.061348671,"prompt_tokens":18,"prompt_time":0.000211569,` +
941
+ `"completion_tokens":439,"completion_time":0.798181818,"total_tokens":457,"total_time":0.798393387}}}\n\n`,
942
+ 'data: [DONE]\n\n',
943
+ ],
944
+ };
945
+ }
946
+
947
+ it('should stream text deltas', async () => {
948
+ prepareStreamResponse({
949
+ content: ['Hello', ', ', 'World!'],
950
+ finish_reason: 'stop',
951
+ });
952
+
953
+ const { stream } = await model.doStream({
954
+ prompt: TEST_PROMPT,
955
+ includeRawChunks: false,
956
+ });
957
+
958
+ // note: space moved to last chunk bc of trimming
959
+ expect(await convertReadableStreamToArray(stream)).toMatchInlineSnapshot(`
960
+ [
961
+ {
962
+ "type": "stream-start",
963
+ "warnings": [],
964
+ },
965
+ {
966
+ "id": "chatcmpl-e7f8e220-656c-4455-a132-dacfc1370798",
967
+ "modelId": "gemma2-9b-it",
968
+ "timestamp": 2023-12-15T16:17:00.000Z,
969
+ "type": "response-metadata",
970
+ },
971
+ {
972
+ "id": "txt-0",
973
+ "type": "text-start",
974
+ },
975
+ {
976
+ "delta": "Hello",
977
+ "id": "txt-0",
978
+ "type": "text-delta",
979
+ },
980
+ {
981
+ "delta": ", ",
982
+ "id": "txt-0",
983
+ "type": "text-delta",
984
+ },
985
+ {
986
+ "delta": "World!",
987
+ "id": "txt-0",
988
+ "type": "text-delta",
989
+ },
990
+ {
991
+ "id": "txt-0",
992
+ "type": "text-end",
993
+ },
994
+ {
995
+ "finishReason": {
996
+ "raw": "stop",
997
+ "unified": "stop",
998
+ },
999
+ "type": "finish",
1000
+ "usage": {
1001
+ "inputTokens": {
1002
+ "cacheRead": undefined,
1003
+ "cacheWrite": undefined,
1004
+ "noCache": 18,
1005
+ "total": 18,
1006
+ },
1007
+ "outputTokens": {
1008
+ "reasoning": undefined,
1009
+ "text": 439,
1010
+ "total": 439,
1011
+ },
1012
+ "raw": {
1013
+ "completion_tokens": 439,
1014
+ "prompt_tokens": 18,
1015
+ "total_tokens": 457,
1016
+ },
1017
+ },
1018
+ },
1019
+ ]
1020
+ `);
1021
+ });
1022
+
1023
+ it('should stream reasoning deltas', async () => {
1024
+ server.urls['https://api.groq.com/openai/v1/chat/completions'].response = {
1025
+ type: 'stream-chunks',
1026
+ chunks: [
1027
+ `data: {"id":"chatcmpl-e7f8e220-656c-4455-a132-dacfc1370798","object":"chat.completion.chunk","created":1702657020,"model":"gemma2-9b-it",` +
1028
+ `"system_fingerprint":null,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null}]}\n\n`,
1029
+ `data: {"id":"chatcmpl-e7f8e220-656c-4455-a132-dacfc1370798","object":"chat.completion.chunk","created":1702657020,"model":"gemma2-9b-it",` +
1030
+ `"system_fingerprint":null,"choices":[{"index":1,"delta":{"reasoning":"I think,"},"finish_reason":null}]}\n\n`,
1031
+ `data: {"id":"chatcmpl-e7f8e220-656c-4455-a132-dacfc1370798","object":"chat.completion.chunk","created":1702657020,"model":"gemma2-9b-it",` +
1032
+ `"system_fingerprint":null,"choices":[{"index":1,"delta":{"reasoning":"therefore I am."},"finish_reason":null}]}\n\n`,
1033
+ `data: {"id":"chatcmpl-e7f8e220-656c-4455-a132-dacfc1370798","object":"chat.completion.chunk","created":1702657020,"model":"gemma2-9b-it",` +
1034
+ `"system_fingerprint":null,"choices":[{"index":1,"delta":{"content":"Hello"},"finish_reason":null}]}\n\n`,
1035
+ `data: {"id":"chatcmpl-e7f8e220-656c-4455-a132-dacfc1370798","object":"chat.completion.chunk","created":1702657020,"model":"gemma2-9b-it",` +
1036
+ `"system_fingerprint":null,"choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}\n\n`,
1037
+ `data: {"id":"chatcmpl-e7f8e220-656c-4455-a132-dacfc1370798","object":"chat.completion.chunk","created":1729171479,"model":"gemma2-9b-it",` +
1038
+ `"system_fingerprint":"fp_10c08bf97d","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}],` +
1039
+ `"x_groq":{"id":"req_01jadadp0femyae9kav1gpkhe8","usage":{"queue_time":0.061348671,"prompt_tokens":18,"prompt_time":0.000211569,` +
1040
+ `"completion_tokens":439,"completion_time":0.798181818,"total_tokens":457,"total_time":0.798393387}}}\n\n`,
1041
+ 'data: [DONE]\n\n',
1042
+ ],
1043
+ };
1044
+
1045
+ const { stream } = await model.doStream({
1046
+ prompt: TEST_PROMPT,
1047
+ includeRawChunks: false,
1048
+ });
1049
+
1050
+ expect(await convertReadableStreamToArray(stream)).toMatchInlineSnapshot(`
1051
+ [
1052
+ {
1053
+ "type": "stream-start",
1054
+ "warnings": [],
1055
+ },
1056
+ {
1057
+ "id": "chatcmpl-e7f8e220-656c-4455-a132-dacfc1370798",
1058
+ "modelId": "gemma2-9b-it",
1059
+ "timestamp": 2023-12-15T16:17:00.000Z,
1060
+ "type": "response-metadata",
1061
+ },
1062
+ {
1063
+ "id": "reasoning-0",
1064
+ "type": "reasoning-start",
1065
+ },
1066
+ {
1067
+ "delta": "I think,",
1068
+ "id": "reasoning-0",
1069
+ "type": "reasoning-delta",
1070
+ },
1071
+ {
1072
+ "delta": "therefore I am.",
1073
+ "id": "reasoning-0",
1074
+ "type": "reasoning-delta",
1075
+ },
1076
+ {
1077
+ "id": "reasoning-0",
1078
+ "type": "reasoning-end",
1079
+ },
1080
+ {
1081
+ "id": "txt-0",
1082
+ "type": "text-start",
1083
+ },
1084
+ {
1085
+ "delta": "Hello",
1086
+ "id": "txt-0",
1087
+ "type": "text-delta",
1088
+ },
1089
+ {
1090
+ "id": "txt-0",
1091
+ "type": "text-end",
1092
+ },
1093
+ {
1094
+ "finishReason": {
1095
+ "raw": "stop",
1096
+ "unified": "stop",
1097
+ },
1098
+ "type": "finish",
1099
+ "usage": {
1100
+ "inputTokens": {
1101
+ "cacheRead": undefined,
1102
+ "cacheWrite": undefined,
1103
+ "noCache": 18,
1104
+ "total": 18,
1105
+ },
1106
+ "outputTokens": {
1107
+ "reasoning": undefined,
1108
+ "text": 439,
1109
+ "total": 439,
1110
+ },
1111
+ "raw": {
1112
+ "completion_tokens": 439,
1113
+ "prompt_tokens": 18,
1114
+ "total_tokens": 457,
1115
+ },
1116
+ },
1117
+ },
1118
+ ]
1119
+ `);
1120
+ });
1121
+
1122
+ it('should stream reasoning tokens from completion_tokens_details', async () => {
1123
+ server.urls['https://api.groq.com/openai/v1/chat/completions'].response = {
1124
+ type: 'stream-chunks',
1125
+ chunks: [
1126
+ `data: {"id":"chatcmpl-e7f8e220-656c-4455-a132-dacfc1370798","object":"chat.completion.chunk","created":1702657020,"model":"openai/gpt-oss-120b",` +
1127
+ `"system_fingerprint":null,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null}]}\n\n`,
1128
+ `data: {"id":"chatcmpl-e7f8e220-656c-4455-a132-dacfc1370798","object":"chat.completion.chunk","created":1702657020,"model":"openai/gpt-oss-120b",` +
1129
+ `"system_fingerprint":null,"choices":[{"index":1,"delta":{"content":"42"},"finish_reason":null}]}\n\n`,
1130
+ `data: {"id":"chatcmpl-e7f8e220-656c-4455-a132-dacfc1370798","object":"chat.completion.chunk","created":1702657020,"model":"openai/gpt-oss-120b",` +
1131
+ `"system_fingerprint":null,"choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}\n\n`,
1132
+ `data: {"id":"chatcmpl-e7f8e220-656c-4455-a132-dacfc1370798","object":"chat.completion.chunk","created":1729171479,"model":"openai/gpt-oss-120b",` +
1133
+ `"system_fingerprint":"fp_10c08bf97d","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}],` +
1134
+ `"x_groq":{"id":"req_01jadadp0femyae9kav1gpkhe8","usage":{"queue_time":0.061348671,"prompt_tokens":79,"prompt_time":0.000211569,` +
1135
+ `"completion_tokens":40,"completion_time":0.798181818,"total_tokens":119,"total_time":0.798393387,` +
1136
+ `"completion_tokens_details":{"reasoning_tokens":21}}}}\n\n`,
1137
+ 'data: [DONE]\n\n',
1138
+ ],
1139
+ };
1140
+
1141
+ const { stream } = await model.doStream({
1142
+ prompt: TEST_PROMPT,
1143
+ includeRawChunks: false,
1144
+ });
1145
+
1146
+ const chunks = await convertReadableStreamToArray(stream);
1147
+ const finishChunk = chunks.find(chunk => chunk.type === 'finish');
1148
+
1149
+ expect(finishChunk).toMatchInlineSnapshot(`
1150
+ {
1151
+ "finishReason": {
1152
+ "raw": "stop",
1153
+ "unified": "stop",
1154
+ },
1155
+ "type": "finish",
1156
+ "usage": {
1157
+ "inputTokens": {
1158
+ "cacheRead": undefined,
1159
+ "cacheWrite": undefined,
1160
+ "noCache": 79,
1161
+ "total": 79,
1162
+ },
1163
+ "outputTokens": {
1164
+ "reasoning": 21,
1165
+ "text": 19,
1166
+ "total": 40,
1167
+ },
1168
+ "raw": {
1169
+ "completion_tokens": 40,
1170
+ "completion_tokens_details": {
1171
+ "reasoning_tokens": 21,
1172
+ },
1173
+ "prompt_tokens": 79,
1174
+ "total_tokens": 119,
1175
+ },
1176
+ },
1177
+ }
1178
+ `);
1179
+ });
1180
+
1181
+ it('should stream tool deltas', async () => {
1182
+ server.urls['https://api.groq.com/openai/v1/chat/completions'].response = {
1183
+ type: 'stream-chunks',
1184
+ chunks: [
1185
+ `data: {"id":"chatcmpl-e7f8e220-656c-4455-a132-dacfc1370798","object":"chat.completion.chunk","created":1711357598,"model":"gemma2-9b-it",` +
1186
+ `"system_fingerprint":"fp_3bc1b5746c","choices":[{"index":0,"delta":{"role":"assistant","content":null,` +
1187
+ `"tool_calls":[{"index":0,"id":"call_O17Uplv4lJvD6DVdIvFFeRMw","type":"function","function":{"name":"test-tool","arguments":""}}]},` +
1188
+ `"finish_reason":null}]}\n\n`,
1189
+ `data: {"id":"chatcmpl-e7f8e220-656c-4455-a132-dacfc1370798","object":"chat.completion.chunk","created":1711357598,"model":"gemma2-9b-it",` +
1190
+ `"system_fingerprint":"fp_3bc1b5746c","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"{\\""}}]},` +
1191
+ `"finish_reason":null}]}\n\n`,
1192
+ `data: {"id":"chatcmpl-e7f8e220-656c-4455-a132-dacfc1370798","object":"chat.completion.chunk","created":1711357598,"model":"gemma2-9b-it",` +
1193
+ `"system_fingerprint":"fp_3bc1b5746c","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"value"}}]},` +
1194
+ `"finish_reason":null}]}\n\n`,
1195
+ `data: {"id":"chatcmpl-e7f8e220-656c-4455-a132-dacfc1370798","object":"chat.completion.chunk","created":1711357598,"model":"gemma2-9b-it",` +
1196
+ `"system_fingerprint":"fp_3bc1b5746c","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\\":\\""}}]},` +
1197
+ `"finish_reason":null}]}\n\n`,
1198
+ `data: {"id":"chatcmpl-e7f8e220-656c-4455-a132-dacfc1370798","object":"chat.completion.chunk","created":1711357598,"model":"gemma2-9b-it",` +
1199
+ `"system_fingerprint":"fp_3bc1b5746c","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"Spark"}}]},` +
1200
+ `"finish_reason":null}]}\n\n`,
1201
+ `data: {"id":"chatcmpl-e7f8e220-656c-4455-a132-dacfc1370798","object":"chat.completion.chunk","created":1711357598,"model":"gemma2-9b-it",` +
1202
+ `"system_fingerprint":"fp_3bc1b5746c","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"le"}}]},` +
1203
+ `"finish_reason":null}]}\n\n`,
1204
+ `data: {"id":"chatcmpl-e7f8e220-656c-4455-a132-dacfc1370798","object":"chat.completion.chunk","created":1711357598,"model":"gemma2-9b-it",` +
1205
+ `"system_fingerprint":"fp_3bc1b5746c","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" Day"}}]},` +
1206
+ `"finish_reason":null}]}\n\n`,
1207
+ `data: {"id":"chatcmpl-e7f8e220-656c-4455-a132-dacfc1370798","object":"chat.completion.chunk","created":1711357598,"model":"gemma2-9b-it",` +
1208
+ `"system_fingerprint":"fp_3bc1b5746c","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\\"}"}}]},` +
1209
+ `"finish_reason":null}]}\n\n`,
1210
+ `data: {"id":"chatcmpl-e7f8e220-656c-4455-a132-dacfc1370798","object":"chat.completion.chunk","created":1729171479,"model":"gemma2-9b-it",` +
1211
+ `"system_fingerprint":"fp_10c08bf97d","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"tool_calls"}],` +
1212
+ `"x_groq":{"id":"req_01jadadp0femyae9kav1gpkhe8","usage":{"queue_time":0.061348671,"prompt_tokens":18,"prompt_time":0.000211569,` +
1213
+ `"completion_tokens":439,"completion_time":0.798181818,"total_tokens":457,"total_time":0.798393387}}}\n\n`,
1214
+ 'data: [DONE]\n\n',
1215
+ ],
1216
+ };
1217
+
1218
+ const { stream } = await model.doStream({
1219
+ tools: [
1220
+ {
1221
+ type: 'function',
1222
+ name: 'test-tool',
1223
+ inputSchema: {
1224
+ type: 'object',
1225
+ properties: { value: { type: 'string' } },
1226
+ required: ['value'],
1227
+ additionalProperties: false,
1228
+ $schema: 'http://json-schema.org/draft-07/schema#',
1229
+ },
1230
+ },
1231
+ ],
1232
+ prompt: TEST_PROMPT,
1233
+ includeRawChunks: false,
1234
+ });
1235
+
1236
+ expect(await convertReadableStreamToArray(stream)).toMatchInlineSnapshot(`
1237
+ [
1238
+ {
1239
+ "type": "stream-start",
1240
+ "warnings": [],
1241
+ },
1242
+ {
1243
+ "id": "chatcmpl-e7f8e220-656c-4455-a132-dacfc1370798",
1244
+ "modelId": "gemma2-9b-it",
1245
+ "timestamp": 2024-03-25T09:06:38.000Z,
1246
+ "type": "response-metadata",
1247
+ },
1248
+ {
1249
+ "id": "call_O17Uplv4lJvD6DVdIvFFeRMw",
1250
+ "toolName": "test-tool",
1251
+ "type": "tool-input-start",
1252
+ },
1253
+ {
1254
+ "delta": "{"",
1255
+ "id": "call_O17Uplv4lJvD6DVdIvFFeRMw",
1256
+ "type": "tool-input-delta",
1257
+ },
1258
+ {
1259
+ "delta": "value",
1260
+ "id": "call_O17Uplv4lJvD6DVdIvFFeRMw",
1261
+ "type": "tool-input-delta",
1262
+ },
1263
+ {
1264
+ "delta": "":"",
1265
+ "id": "call_O17Uplv4lJvD6DVdIvFFeRMw",
1266
+ "type": "tool-input-delta",
1267
+ },
1268
+ {
1269
+ "delta": "Spark",
1270
+ "id": "call_O17Uplv4lJvD6DVdIvFFeRMw",
1271
+ "type": "tool-input-delta",
1272
+ },
1273
+ {
1274
+ "delta": "le",
1275
+ "id": "call_O17Uplv4lJvD6DVdIvFFeRMw",
1276
+ "type": "tool-input-delta",
1277
+ },
1278
+ {
1279
+ "delta": " Day",
1280
+ "id": "call_O17Uplv4lJvD6DVdIvFFeRMw",
1281
+ "type": "tool-input-delta",
1282
+ },
1283
+ {
1284
+ "delta": ""}",
1285
+ "id": "call_O17Uplv4lJvD6DVdIvFFeRMw",
1286
+ "type": "tool-input-delta",
1287
+ },
1288
+ {
1289
+ "id": "call_O17Uplv4lJvD6DVdIvFFeRMw",
1290
+ "type": "tool-input-end",
1291
+ },
1292
+ {
1293
+ "input": "{"value":"Sparkle Day"}",
1294
+ "toolCallId": "call_O17Uplv4lJvD6DVdIvFFeRMw",
1295
+ "toolName": "test-tool",
1296
+ "type": "tool-call",
1297
+ },
1298
+ {
1299
+ "finishReason": {
1300
+ "raw": "tool_calls",
1301
+ "unified": "tool-calls",
1302
+ },
1303
+ "type": "finish",
1304
+ "usage": {
1305
+ "inputTokens": {
1306
+ "cacheRead": undefined,
1307
+ "cacheWrite": undefined,
1308
+ "noCache": 18,
1309
+ "total": 18,
1310
+ },
1311
+ "outputTokens": {
1312
+ "reasoning": undefined,
1313
+ "text": 439,
1314
+ "total": 439,
1315
+ },
1316
+ "raw": {
1317
+ "completion_tokens": 439,
1318
+ "prompt_tokens": 18,
1319
+ "total_tokens": 457,
1320
+ },
1321
+ },
1322
+ },
1323
+ ]
1324
+ `);
1325
+ });
1326
+
1327
+ it('should stream tool call deltas when tool call arguments are passed in the first chunk', async () => {
1328
+ server.urls['https://api.groq.com/openai/v1/chat/completions'].response = {
1329
+ type: 'stream-chunks',
1330
+ chunks: [
1331
+ `data: {"id":"chatcmpl-e7f8e220-656c-4455-a132-dacfc1370798","object":"chat.completion.chunk","created":1711357598,"model":"gemma2-9b-it",` +
1332
+ `"system_fingerprint":"fp_3bc1b5746c","choices":[{"index":0,"delta":{"role":"assistant","content":null,` +
1333
+ `"tool_calls":[{"index":0,"id":"call_O17Uplv4lJvD6DVdIvFFeRMw","type":"function","function":{"name":"test-tool","arguments":"{\\""}}]},` +
1334
+ `"finish_reason":null}]}\n\n`,
1335
+ `data: {"id":"chatcmpl-e7f8e220-656c-4455-a132-dacfc1370798","object":"chat.completion.chunk","created":1711357598,"model":"gemma2-9b-it",` +
1336
+ `"system_fingerprint":"fp_3bc1b5746c","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"va"}}]},` +
1337
+ `"finish_reason":null}]}\n\n`,
1338
+ `data: {"id":"chatcmpl-e7f8e220-656c-4455-a132-dacfc1370798","object":"chat.completion.chunk","created":1711357598,"model":"gemma2-9b-it",` +
1339
+ `"system_fingerprint":"fp_3bc1b5746c","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"lue"}}]},` +
1340
+ `"finish_reason":null}]}\n\n`,
1341
+ `data: {"id":"chatcmpl-e7f8e220-656c-4455-a132-dacfc1370798","object":"chat.completion.chunk","created":1711357598,"model":"gemma2-9b-it",` +
1342
+ `"system_fingerprint":"fp_3bc1b5746c","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\\":\\""}}]},` +
1343
+ `"finish_reason":null}]}\n\n`,
1344
+ `data: {"id":"chatcmpl-e7f8e220-656c-4455-a132-dacfc1370798","object":"chat.completion.chunk","created":1711357598,"model":"gemma2-9b-it",` +
1345
+ `"system_fingerprint":"fp_3bc1b5746c","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"Spark"}}]},` +
1346
+ `"finish_reason":null}]}\n\n`,
1347
+ `data: {"id":"chatcmpl-e7f8e220-656c-4455-a132-dacfc1370798","object":"chat.completion.chunk","created":1711357598,"model":"gemma2-9b-it",` +
1348
+ `"system_fingerprint":"fp_3bc1b5746c","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"le"}}]},` +
1349
+ `"finish_reason":null}]}\n\n`,
1350
+ `data: {"id":"chatcmpl-e7f8e220-656c-4455-a132-dacfc1370798","object":"chat.completion.chunk","created":1711357598,"model":"gemma2-9b-it",` +
1351
+ `"system_fingerprint":"fp_3bc1b5746c","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" Day"}}]},` +
1352
+ `"finish_reason":null}]}\n\n`,
1353
+ `data: {"id":"chatcmpl-e7f8e220-656c-4455-a132-dacfc1370798","object":"chat.completion.chunk","created":1711357598,"model":"gemma2-9b-it",` +
1354
+ `"system_fingerprint":"fp_3bc1b5746c","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\\"}"}}]},` +
1355
+ `"finish_reason":null}]}\n\n`,
1356
+ `data: {"id":"chatcmpl-e7f8e220-656c-4455-a132-dacfc1370798","object":"chat.completion.chunk","created":1729171479,"model":"gemma2-9b-it",` +
1357
+ `"system_fingerprint":"fp_10c08bf97d","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"tool_calls"}],` +
1358
+ `"x_groq":{"id":"req_01jadadp0femyae9kav1gpkhe8","usage":{"queue_time":0.061348671,"prompt_tokens":18,"prompt_time":0.000211569,` +
1359
+ `"completion_tokens":439,"completion_time":0.798181818,"total_tokens":457,"total_time":0.798393387}}}\n\n`,
1360
+ 'data: [DONE]\n\n',
1361
+ ],
1362
+ };
1363
+
1364
+ const { stream } = await model.doStream({
1365
+ tools: [
1366
+ {
1367
+ type: 'function',
1368
+ name: 'test-tool',
1369
+ inputSchema: {
1370
+ type: 'object',
1371
+ properties: { value: { type: 'string' } },
1372
+ required: ['value'],
1373
+ additionalProperties: false,
1374
+ $schema: 'http://json-schema.org/draft-07/schema#',
1375
+ },
1376
+ },
1377
+ ],
1378
+ prompt: TEST_PROMPT,
1379
+ includeRawChunks: false,
1380
+ });
1381
+
1382
+ expect(await convertReadableStreamToArray(stream)).toMatchInlineSnapshot(`
1383
+ [
1384
+ {
1385
+ "type": "stream-start",
1386
+ "warnings": [],
1387
+ },
1388
+ {
1389
+ "id": "chatcmpl-e7f8e220-656c-4455-a132-dacfc1370798",
1390
+ "modelId": "gemma2-9b-it",
1391
+ "timestamp": 2024-03-25T09:06:38.000Z,
1392
+ "type": "response-metadata",
1393
+ },
1394
+ {
1395
+ "id": "call_O17Uplv4lJvD6DVdIvFFeRMw",
1396
+ "toolName": "test-tool",
1397
+ "type": "tool-input-start",
1398
+ },
1399
+ {
1400
+ "delta": "{"",
1401
+ "id": "call_O17Uplv4lJvD6DVdIvFFeRMw",
1402
+ "type": "tool-input-delta",
1403
+ },
1404
+ {
1405
+ "delta": "va",
1406
+ "id": "call_O17Uplv4lJvD6DVdIvFFeRMw",
1407
+ "type": "tool-input-delta",
1408
+ },
1409
+ {
1410
+ "delta": "lue",
1411
+ "id": "call_O17Uplv4lJvD6DVdIvFFeRMw",
1412
+ "type": "tool-input-delta",
1413
+ },
1414
+ {
1415
+ "delta": "":"",
1416
+ "id": "call_O17Uplv4lJvD6DVdIvFFeRMw",
1417
+ "type": "tool-input-delta",
1418
+ },
1419
+ {
1420
+ "delta": "Spark",
1421
+ "id": "call_O17Uplv4lJvD6DVdIvFFeRMw",
1422
+ "type": "tool-input-delta",
1423
+ },
1424
+ {
1425
+ "delta": "le",
1426
+ "id": "call_O17Uplv4lJvD6DVdIvFFeRMw",
1427
+ "type": "tool-input-delta",
1428
+ },
1429
+ {
1430
+ "delta": " Day",
1431
+ "id": "call_O17Uplv4lJvD6DVdIvFFeRMw",
1432
+ "type": "tool-input-delta",
1433
+ },
1434
+ {
1435
+ "delta": ""}",
1436
+ "id": "call_O17Uplv4lJvD6DVdIvFFeRMw",
1437
+ "type": "tool-input-delta",
1438
+ },
1439
+ {
1440
+ "id": "call_O17Uplv4lJvD6DVdIvFFeRMw",
1441
+ "type": "tool-input-end",
1442
+ },
1443
+ {
1444
+ "input": "{"value":"Sparkle Day"}",
1445
+ "toolCallId": "call_O17Uplv4lJvD6DVdIvFFeRMw",
1446
+ "toolName": "test-tool",
1447
+ "type": "tool-call",
1448
+ },
1449
+ {
1450
+ "finishReason": {
1451
+ "raw": "tool_calls",
1452
+ "unified": "tool-calls",
1453
+ },
1454
+ "type": "finish",
1455
+ "usage": {
1456
+ "inputTokens": {
1457
+ "cacheRead": undefined,
1458
+ "cacheWrite": undefined,
1459
+ "noCache": 18,
1460
+ "total": 18,
1461
+ },
1462
+ "outputTokens": {
1463
+ "reasoning": undefined,
1464
+ "text": 439,
1465
+ "total": 439,
1466
+ },
1467
+ "raw": {
1468
+ "completion_tokens": 439,
1469
+ "prompt_tokens": 18,
1470
+ "total_tokens": 457,
1471
+ },
1472
+ },
1473
+ },
1474
+ ]
1475
+ `);
1476
+ });
1477
+
1478
+ it('should not duplicate tool calls when there is an additional empty chunk after the tool call has been completed', async () => {
1479
+ server.urls['https://api.groq.com/openai/v1/chat/completions'].response = {
1480
+ type: 'stream-chunks',
1481
+ chunks: [
1482
+ `data: {"id":"chat-2267f7e2910a4254bac0650ba74cfc1c","object":"chat.completion.chunk","created":1733162241,` +
1483
+ `"model":"meta/llama-3.1-8b-instruct:fp8","choices":[{"index":0,"delta":{"role":"assistant","content":""},"logprobs":null,"finish_reason":null}],` +
1484
+ `"usage":{"prompt_tokens":226,"total_tokens":226,"completion_tokens":0}}\n\n`,
1485
+ `data: {"id":"chat-2267f7e2910a4254bac0650ba74cfc1c","object":"chat.completion.chunk","created":1733162241,` +
1486
+ `"model":"meta/llama-3.1-8b-instruct:fp8","choices":[{"index":0,"delta":{"tool_calls":[{"id":"chatcmpl-tool-b3b307239370432d9910d4b79b4dbbaa",` +
1487
+ `"type":"function","index":0,"function":{"name":"searchGoogle"}}]},"logprobs":null,"finish_reason":null}],` +
1488
+ `"usage":{"prompt_tokens":226,"total_tokens":233,"completion_tokens":7}}\n\n`,
1489
+ `data: {"id":"chat-2267f7e2910a4254bac0650ba74cfc1c","object":"chat.completion.chunk","created":1733162241,` +
1490
+ `"model":"meta/llama-3.1-8b-instruct:fp8","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,` +
1491
+ `"function":{"arguments":"{\\"query\\": \\""}}]},"logprobs":null,"finish_reason":null}],` +
1492
+ `"usage":{"prompt_tokens":226,"total_tokens":241,"completion_tokens":15}}\n\n`,
1493
+ `data: {"id":"chat-2267f7e2910a4254bac0650ba74cfc1c","object":"chat.completion.chunk","created":1733162241,` +
1494
+ `"model":"meta/llama-3.1-8b-instruct:fp8","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,` +
1495
+ `"function":{"arguments":"latest"}}]},"logprobs":null,"finish_reason":null}],` +
1496
+ `"usage":{"prompt_tokens":226,"total_tokens":242,"completion_tokens":16}}\n\n`,
1497
+ `data: {"id":"chat-2267f7e2910a4254bac0650ba74cfc1c","object":"chat.completion.chunk","created":1733162241,` +
1498
+ `"model":"meta/llama-3.1-8b-instruct:fp8","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,` +
1499
+ `"function":{"arguments":" news"}}]},"logprobs":null,"finish_reason":null}],` +
1500
+ `"usage":{"prompt_tokens":226,"total_tokens":243,"completion_tokens":17}}\n\n`,
1501
+ `data: {"id":"chat-2267f7e2910a4254bac0650ba74cfc1c","object":"chat.completion.chunk","created":1733162241,` +
1502
+ `"model":"meta/llama-3.1-8b-instruct:fp8","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,` +
1503
+ `"function":{"arguments":" on"}}]},"logprobs":null,"finish_reason":null}],` +
1504
+ `"usage":{"prompt_tokens":226,"total_tokens":244,"completion_tokens":18}}\n\n`,
1505
+ `data: {"id":"chat-2267f7e2910a4254bac0650ba74cfc1c","object":"chat.completion.chunk","created":1733162241,` +
1506
+ `"model":"meta/llama-3.1-8b-instruct:fp8","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,` +
1507
+ `"function":{"arguments":" ai\\"}"}}]},"logprobs":null,"finish_reason":null}],` +
1508
+ `"usage":{"prompt_tokens":226,"total_tokens":245,"completion_tokens":19}}\n\n`,
1509
+ // empty arguments chunk after the tool call has already been finished:
1510
+ `data: {"id":"chat-2267f7e2910a4254bac0650ba74cfc1c","object":"chat.completion.chunk","created":1733162241,` +
1511
+ `"model":"meta/llama-3.1-8b-instruct:fp8","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,` +
1512
+ `"function":{"arguments":""}}]},"logprobs":null,"finish_reason":"tool_calls","stop_reason":128008}],` +
1513
+ `"usage":{"prompt_tokens":226,"total_tokens":246,"completion_tokens":20}}\n\n`,
1514
+ `data: {"id":"chat-2267f7e2910a4254bac0650ba74cfc1c","object":"chat.completion.chunk","created":1733162241,` +
1515
+ `"model":"meta/llama-3.1-8b-instruct:fp8","choices":[],` +
1516
+ `"usage":{"prompt_tokens":226,"total_tokens":246,"completion_tokens":20}}\n\n`,
1517
+ `data: [DONE]\n\n`,
1518
+ ],
1519
+ };
1520
+
1521
+ const { stream } = await model.doStream({
1522
+ tools: [
1523
+ {
1524
+ type: 'function',
1525
+ name: 'searchGoogle',
1526
+ inputSchema: {
1527
+ type: 'object',
1528
+ properties: { query: { type: 'string' } },
1529
+ required: ['query'],
1530
+ additionalProperties: false,
1531
+ $schema: 'http://json-schema.org/draft-07/schema#',
1532
+ },
1533
+ },
1534
+ ],
1535
+ prompt: TEST_PROMPT,
1536
+ includeRawChunks: false,
1537
+ });
1538
+
1539
+ expect(await convertReadableStreamToArray(stream)).toMatchInlineSnapshot(`
1540
+ [
1541
+ {
1542
+ "type": "stream-start",
1543
+ "warnings": [],
1544
+ },
1545
+ {
1546
+ "id": "chat-2267f7e2910a4254bac0650ba74cfc1c",
1547
+ "modelId": "meta/llama-3.1-8b-instruct:fp8",
1548
+ "timestamp": 2024-12-02T17:57:21.000Z,
1549
+ "type": "response-metadata",
1550
+ },
1551
+ {
1552
+ "id": "chatcmpl-tool-b3b307239370432d9910d4b79b4dbbaa",
1553
+ "toolName": "searchGoogle",
1554
+ "type": "tool-input-start",
1555
+ },
1556
+ {
1557
+ "delta": "{"query": "",
1558
+ "id": "chatcmpl-tool-b3b307239370432d9910d4b79b4dbbaa",
1559
+ "type": "tool-input-delta",
1560
+ },
1561
+ {
1562
+ "delta": "latest",
1563
+ "id": "chatcmpl-tool-b3b307239370432d9910d4b79b4dbbaa",
1564
+ "type": "tool-input-delta",
1565
+ },
1566
+ {
1567
+ "delta": " news",
1568
+ "id": "chatcmpl-tool-b3b307239370432d9910d4b79b4dbbaa",
1569
+ "type": "tool-input-delta",
1570
+ },
1571
+ {
1572
+ "delta": " on",
1573
+ "id": "chatcmpl-tool-b3b307239370432d9910d4b79b4dbbaa",
1574
+ "type": "tool-input-delta",
1575
+ },
1576
+ {
1577
+ "delta": " ai"}",
1578
+ "id": "chatcmpl-tool-b3b307239370432d9910d4b79b4dbbaa",
1579
+ "type": "tool-input-delta",
1580
+ },
1581
+ {
1582
+ "id": "chatcmpl-tool-b3b307239370432d9910d4b79b4dbbaa",
1583
+ "type": "tool-input-end",
1584
+ },
1585
+ {
1586
+ "input": "{"query": "latest news on ai"}",
1587
+ "toolCallId": "chatcmpl-tool-b3b307239370432d9910d4b79b4dbbaa",
1588
+ "toolName": "searchGoogle",
1589
+ "type": "tool-call",
1590
+ },
1591
+ {
1592
+ "finishReason": {
1593
+ "raw": "tool_calls",
1594
+ "unified": "tool-calls",
1595
+ },
1596
+ "type": "finish",
1597
+ "usage": {
1598
+ "inputTokens": {
1599
+ "cacheRead": undefined,
1600
+ "cacheWrite": undefined,
1601
+ "noCache": undefined,
1602
+ "total": undefined,
1603
+ },
1604
+ "outputTokens": {
1605
+ "reasoning": undefined,
1606
+ "text": undefined,
1607
+ "total": undefined,
1608
+ },
1609
+ "raw": undefined,
1610
+ },
1611
+ },
1612
+ ]
1613
+ `);
1614
+ });
1615
+
1616
+ it('should stream tool call that is sent in one chunk', async () => {
1617
+ server.urls['https://api.groq.com/openai/v1/chat/completions'].response = {
1618
+ type: 'stream-chunks',
1619
+ chunks: [
1620
+ `data: {"id":"chatcmpl-e7f8e220-656c-4455-a132-dacfc1370798","object":"chat.completion.chunk","created":1711357598,"model":"gemma2-9b-it",` +
1621
+ `"system_fingerprint":"fp_3bc1b5746c","choices":[{"index":0,"delta":{"role":"assistant","content":null,` +
1622
+ `"tool_calls":[{"index":0,"id":"call_O17Uplv4lJvD6DVdIvFFeRMw","type":"function","function":{"name":"test-tool","arguments":"{\\"value\\":\\"Sparkle Day\\"}"}}]},` +
1623
+ `"finish_reason":null}]}\n\n`,
1624
+ `data: {"id":"chatcmpl-e7f8e220-656c-4455-a132-dacfc1370798","object":"chat.completion.chunk","created":1729171479,"model":"gemma2-9b-it",` +
1625
+ `"system_fingerprint":"fp_10c08bf97d","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"tool_calls"}],` +
1626
+ `"x_groq":{"id":"req_01jadadp0femyae9kav1gpkhe8","usage":{"queue_time":0.061348671,"prompt_tokens":18,"prompt_time":0.000211569,` +
1627
+ `"completion_tokens":439,"completion_time":0.798181818,"total_tokens":457,"total_time":0.798393387}}}\n\n`,
1628
+ 'data: [DONE]\n\n',
1629
+ ],
1630
+ };
1631
+
1632
+ const { stream } = await model.doStream({
1633
+ tools: [
1634
+ {
1635
+ type: 'function',
1636
+ name: 'test-tool',
1637
+ inputSchema: {
1638
+ type: 'object',
1639
+ properties: { value: { type: 'string' } },
1640
+ required: ['value'],
1641
+ additionalProperties: false,
1642
+ $schema: 'http://json-schema.org/draft-07/schema#',
1643
+ },
1644
+ },
1645
+ ],
1646
+ prompt: TEST_PROMPT,
1647
+ includeRawChunks: false,
1648
+ });
1649
+
1650
+ expect(await convertReadableStreamToArray(stream)).toMatchInlineSnapshot(`
1651
+ [
1652
+ {
1653
+ "type": "stream-start",
1654
+ "warnings": [],
1655
+ },
1656
+ {
1657
+ "id": "chatcmpl-e7f8e220-656c-4455-a132-dacfc1370798",
1658
+ "modelId": "gemma2-9b-it",
1659
+ "timestamp": 2024-03-25T09:06:38.000Z,
1660
+ "type": "response-metadata",
1661
+ },
1662
+ {
1663
+ "id": "call_O17Uplv4lJvD6DVdIvFFeRMw",
1664
+ "toolName": "test-tool",
1665
+ "type": "tool-input-start",
1666
+ },
1667
+ {
1668
+ "delta": "{"value":"Sparkle Day"}",
1669
+ "id": "call_O17Uplv4lJvD6DVdIvFFeRMw",
1670
+ "type": "tool-input-delta",
1671
+ },
1672
+ {
1673
+ "id": "call_O17Uplv4lJvD6DVdIvFFeRMw",
1674
+ "type": "tool-input-end",
1675
+ },
1676
+ {
1677
+ "input": "{"value":"Sparkle Day"}",
1678
+ "toolCallId": "call_O17Uplv4lJvD6DVdIvFFeRMw",
1679
+ "toolName": "test-tool",
1680
+ "type": "tool-call",
1681
+ },
1682
+ {
1683
+ "finishReason": {
1684
+ "raw": "tool_calls",
1685
+ "unified": "tool-calls",
1686
+ },
1687
+ "type": "finish",
1688
+ "usage": {
1689
+ "inputTokens": {
1690
+ "cacheRead": undefined,
1691
+ "cacheWrite": undefined,
1692
+ "noCache": 18,
1693
+ "total": 18,
1694
+ },
1695
+ "outputTokens": {
1696
+ "reasoning": undefined,
1697
+ "text": 439,
1698
+ "total": 439,
1699
+ },
1700
+ "raw": {
1701
+ "completion_tokens": 439,
1702
+ "prompt_tokens": 18,
1703
+ "total_tokens": 457,
1704
+ },
1705
+ },
1706
+ },
1707
+ ]
1708
+ `);
1709
+ });
1710
+
1711
+ it('should handle error stream parts', async () => {
1712
+ server.urls['https://api.groq.com/openai/v1/chat/completions'].response = {
1713
+ type: 'stream-chunks',
1714
+ chunks: [
1715
+ `data: {"error":{"message": "The server had an error processing your request. Sorry about that!","type":"invalid_request_error"}}\n\n`,
1716
+ 'data: [DONE]\n\n',
1717
+ ],
1718
+ };
1719
+
1720
+ const { stream } = await model.doStream({
1721
+ prompt: TEST_PROMPT,
1722
+ includeRawChunks: false,
1723
+ });
1724
+
1725
+ expect(await convertReadableStreamToArray(stream)).toMatchInlineSnapshot(`
1726
+ [
1727
+ {
1728
+ "type": "stream-start",
1729
+ "warnings": [],
1730
+ },
1731
+ {
1732
+ "error": {
1733
+ "message": "The server had an error processing your request. Sorry about that!",
1734
+ "type": "invalid_request_error",
1735
+ },
1736
+ "type": "error",
1737
+ },
1738
+ {
1739
+ "finishReason": {
1740
+ "raw": undefined,
1741
+ "unified": "error",
1742
+ },
1743
+ "type": "finish",
1744
+ "usage": {
1745
+ "inputTokens": {
1746
+ "cacheRead": undefined,
1747
+ "cacheWrite": undefined,
1748
+ "noCache": undefined,
1749
+ "total": undefined,
1750
+ },
1751
+ "outputTokens": {
1752
+ "reasoning": undefined,
1753
+ "text": undefined,
1754
+ "total": undefined,
1755
+ },
1756
+ "raw": undefined,
1757
+ },
1758
+ },
1759
+ ]
1760
+ `);
1761
+ });
1762
+
1763
+ it.skipIf(isNodeVersion(20))(
1764
+ 'should handle unparsable stream parts',
1765
+ async () => {
1766
+ server.urls['https://api.groq.com/openai/v1/chat/completions'].response =
1767
+ {
1768
+ type: 'stream-chunks',
1769
+ chunks: [`data: {unparsable}\n\n`, 'data: [DONE]\n\n'],
1770
+ };
1771
+
1772
+ const { stream } = await model.doStream({
1773
+ prompt: TEST_PROMPT,
1774
+ includeRawChunks: false,
1775
+ });
1776
+
1777
+ expect(await convertReadableStreamToArray(stream)).toMatchInlineSnapshot(`
1778
+ [
1779
+ {
1780
+ "type": "stream-start",
1781
+ "warnings": [],
1782
+ },
1783
+ {
1784
+ "error": [AI_JSONParseError: JSON parsing failed: Text: {unparsable}.
1785
+ Error message: Expected property name or '}' in JSON at position 1 (line 1 column 2)],
1786
+ "type": "error",
1787
+ },
1788
+ {
1789
+ "finishReason": {
1790
+ "raw": undefined,
1791
+ "unified": "error",
1792
+ },
1793
+ "type": "finish",
1794
+ "usage": {
1795
+ "inputTokens": {
1796
+ "cacheRead": undefined,
1797
+ "cacheWrite": undefined,
1798
+ "noCache": undefined,
1799
+ "total": undefined,
1800
+ },
1801
+ "outputTokens": {
1802
+ "reasoning": undefined,
1803
+ "text": undefined,
1804
+ "total": undefined,
1805
+ },
1806
+ "raw": undefined,
1807
+ },
1808
+ },
1809
+ ]
1810
+ `);
1811
+ },
1812
+ );
1813
+
1814
+ it('should expose the raw response headers', async () => {
1815
+ prepareStreamResponse({
1816
+ headers: {
1817
+ 'test-header': 'test-value',
1818
+ },
1819
+ });
1820
+
1821
+ const { response } = await model.doStream({
1822
+ prompt: TEST_PROMPT,
1823
+ includeRawChunks: false,
1824
+ });
1825
+
1826
+ expect(response?.headers).toStrictEqual({
1827
+ // default headers:
1828
+ 'content-type': 'text/event-stream',
1829
+ 'cache-control': 'no-cache',
1830
+ connection: 'keep-alive',
1831
+
1832
+ // custom header
1833
+ 'test-header': 'test-value',
1834
+ });
1835
+ });
1836
+
1837
+ it('should pass the messages and the model', async () => {
1838
+ prepareStreamResponse({ content: [] });
1839
+
1840
+ await model.doStream({
1841
+ prompt: TEST_PROMPT,
1842
+ includeRawChunks: false,
1843
+ });
1844
+
1845
+ expect(await server.calls[0].requestBodyJson).toStrictEqual({
1846
+ stream: true,
1847
+ model: 'gemma2-9b-it',
1848
+ messages: [{ role: 'user', content: 'Hello' }],
1849
+ });
1850
+ });
1851
+
1852
+ it('should pass headers', async () => {
1853
+ prepareStreamResponse({ content: [] });
1854
+
1855
+ const provider = createGroq({
1856
+ apiKey: 'test-api-key',
1857
+ headers: {
1858
+ 'Custom-Provider-Header': 'provider-header-value',
1859
+ },
1860
+ });
1861
+
1862
+ await provider('gemma2-9b-it').doStream({
1863
+ prompt: TEST_PROMPT,
1864
+ includeRawChunks: false,
1865
+ headers: {
1866
+ 'Custom-Request-Header': 'request-header-value',
1867
+ },
1868
+ });
1869
+
1870
+ expect(server.calls[0].requestHeaders).toStrictEqual({
1871
+ authorization: 'Bearer test-api-key',
1872
+ 'content-type': 'application/json',
1873
+ 'custom-provider-header': 'provider-header-value',
1874
+ 'custom-request-header': 'request-header-value',
1875
+ });
1876
+ });
1877
+
1878
+ it('should send request body', async () => {
1879
+ prepareStreamResponse({ content: [] });
1880
+
1881
+ const { request } = await model.doStream({
1882
+ prompt: TEST_PROMPT,
1883
+ includeRawChunks: false,
1884
+ });
1885
+
1886
+ expect(request).toStrictEqual({
1887
+ body: '{"model":"gemma2-9b-it","messages":[{"role":"user","content":"Hello"}],"stream":true}',
1888
+ });
1889
+ });
1890
+ });
1891
+
1892
+ describe('doStream with raw chunks', () => {
1893
+ it('should stream raw chunks when includeRawChunks is true', async () => {
1894
+ server.urls['https://api.groq.com/openai/v1/chat/completions'].response = {
1895
+ type: 'stream-chunks',
1896
+ chunks: [
1897
+ `data: {"id":"chatcmpl-123","object":"chat.completion.chunk","created":1234567890,"model":"gemma2-9b-it","choices":[{"index":0,"delta":{"content":"Hello"},"finish_reason":null}]}\n\n`,
1898
+ `data: {"id":"chatcmpl-456","object":"chat.completion.chunk","created":1234567890,"model":"gemma2-9b-it","choices":[{"index":0,"delta":{"content":" world"},"finish_reason":null}]}\n\n`,
1899
+ `data: {"id":"chatcmpl-789","object":"chat.completion.chunk","created":1234567890,"model":"gemma2-9b-it","choices":[{"index":0,"delta":{},"finish_reason":"stop"}],"x_groq":{"usage":{"prompt_tokens":10,"completion_tokens":5,"total_tokens":15}}}\n\n`,
1900
+ 'data: [DONE]\n\n',
1901
+ ],
1902
+ };
1903
+
1904
+ const { stream } = await model.doStream({
1905
+ prompt: TEST_PROMPT,
1906
+ includeRawChunks: true,
1907
+ });
1908
+
1909
+ const chunks = await convertReadableStreamToArray(stream);
1910
+
1911
+ expect(chunks).toMatchInlineSnapshot(`
1912
+ [
1913
+ {
1914
+ "type": "stream-start",
1915
+ "warnings": [],
1916
+ },
1917
+ {
1918
+ "rawValue": {
1919
+ "choices": [
1920
+ {
1921
+ "delta": {
1922
+ "content": "Hello",
1923
+ },
1924
+ "finish_reason": null,
1925
+ "index": 0,
1926
+ },
1927
+ ],
1928
+ "created": 1234567890,
1929
+ "id": "chatcmpl-123",
1930
+ "model": "gemma2-9b-it",
1931
+ "object": "chat.completion.chunk",
1932
+ },
1933
+ "type": "raw",
1934
+ },
1935
+ {
1936
+ "id": "chatcmpl-123",
1937
+ "modelId": "gemma2-9b-it",
1938
+ "timestamp": 2009-02-13T23:31:30.000Z,
1939
+ "type": "response-metadata",
1940
+ },
1941
+ {
1942
+ "id": "txt-0",
1943
+ "type": "text-start",
1944
+ },
1945
+ {
1946
+ "delta": "Hello",
1947
+ "id": "txt-0",
1948
+ "type": "text-delta",
1949
+ },
1950
+ {
1951
+ "rawValue": {
1952
+ "choices": [
1953
+ {
1954
+ "delta": {
1955
+ "content": " world",
1956
+ },
1957
+ "finish_reason": null,
1958
+ "index": 0,
1959
+ },
1960
+ ],
1961
+ "created": 1234567890,
1962
+ "id": "chatcmpl-456",
1963
+ "model": "gemma2-9b-it",
1964
+ "object": "chat.completion.chunk",
1965
+ },
1966
+ "type": "raw",
1967
+ },
1968
+ {
1969
+ "delta": " world",
1970
+ "id": "txt-0",
1971
+ "type": "text-delta",
1972
+ },
1973
+ {
1974
+ "rawValue": {
1975
+ "choices": [
1976
+ {
1977
+ "delta": {},
1978
+ "finish_reason": "stop",
1979
+ "index": 0,
1980
+ },
1981
+ ],
1982
+ "created": 1234567890,
1983
+ "id": "chatcmpl-789",
1984
+ "model": "gemma2-9b-it",
1985
+ "object": "chat.completion.chunk",
1986
+ "x_groq": {
1987
+ "usage": {
1988
+ "completion_tokens": 5,
1989
+ "prompt_tokens": 10,
1990
+ "total_tokens": 15,
1991
+ },
1992
+ },
1993
+ },
1994
+ "type": "raw",
1995
+ },
1996
+ {
1997
+ "id": "txt-0",
1998
+ "type": "text-end",
1999
+ },
2000
+ {
2001
+ "finishReason": {
2002
+ "raw": "stop",
2003
+ "unified": "stop",
2004
+ },
2005
+ "type": "finish",
2006
+ "usage": {
2007
+ "inputTokens": {
2008
+ "cacheRead": undefined,
2009
+ "cacheWrite": undefined,
2010
+ "noCache": 10,
2011
+ "total": 10,
2012
+ },
2013
+ "outputTokens": {
2014
+ "reasoning": undefined,
2015
+ "text": 5,
2016
+ "total": 5,
2017
+ },
2018
+ "raw": {
2019
+ "completion_tokens": 5,
2020
+ "prompt_tokens": 10,
2021
+ "total_tokens": 15,
2022
+ },
2023
+ },
2024
+ },
2025
+ ]
2026
+ `);
2027
+ });
2028
+ });