@ai-sdk/perplexity 3.0.10 → 3.0.12

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 CHANGED
@@ -1,5 +1,21 @@
1
1
  # @ai-sdk/perplexity
2
2
 
3
+ ## 3.0.12
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [462ad00]
8
+ - @ai-sdk/provider-utils@4.0.10
9
+
10
+ ## 3.0.11
11
+
12
+ ### Patch Changes
13
+
14
+ - 4de5a1d: chore: excluded tests from src folder in npm package
15
+ - Updated dependencies [4de5a1d]
16
+ - @ai-sdk/provider@3.0.5
17
+ - @ai-sdk/provider-utils@4.0.9
18
+
3
19
  ## 3.0.10
4
20
 
5
21
  ### Patch Changes
package/dist/index.js CHANGED
@@ -479,7 +479,7 @@ var errorToMessage = (data) => {
479
479
  };
480
480
 
481
481
  // src/version.ts
482
- var VERSION = true ? "3.0.10" : "0.0.0-test";
482
+ var VERSION = true ? "3.0.12" : "0.0.0-test";
483
483
 
484
484
  // src/perplexity-provider.ts
485
485
  function createPerplexity(options = {}) {
package/dist/index.mjs CHANGED
@@ -466,7 +466,7 @@ var errorToMessage = (data) => {
466
466
  };
467
467
 
468
468
  // src/version.ts
469
- var VERSION = true ? "3.0.10" : "0.0.0-test";
469
+ var VERSION = true ? "3.0.12" : "0.0.0-test";
470
470
 
471
471
  // src/perplexity-provider.ts
472
472
  function createPerplexity(options = {}) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-sdk/perplexity",
3
- "version": "3.0.10",
3
+ "version": "3.0.12",
4
4
  "license": "Apache-2.0",
5
5
  "sideEffects": false,
6
6
  "main": "./dist/index.js",
@@ -10,6 +10,10 @@
10
10
  "dist/**/*",
11
11
  "docs/**/*",
12
12
  "src",
13
+ "!src/**/*.test.ts",
14
+ "!src/**/*.test-d.ts",
15
+ "!src/**/__snapshots__",
16
+ "!src/**/__fixtures__",
13
17
  "CHANGELOG.md",
14
18
  "README.md"
15
19
  ],
@@ -25,15 +29,15 @@
25
29
  }
26
30
  },
27
31
  "dependencies": {
28
- "@ai-sdk/provider": "3.0.4",
29
- "@ai-sdk/provider-utils": "4.0.8"
32
+ "@ai-sdk/provider": "3.0.5",
33
+ "@ai-sdk/provider-utils": "4.0.10"
30
34
  },
31
35
  "devDependencies": {
32
36
  "@types/node": "20.17.24",
33
37
  "tsup": "^8",
34
38
  "typescript": "5.8.3",
35
39
  "zod": "3.25.76",
36
- "@ai-sdk/test-server": "1.0.2",
40
+ "@ai-sdk/test-server": "1.0.3",
37
41
  "@vercel/ai-tsconfig": "0.0.0"
38
42
  },
39
43
  "peerDependencies": {
@@ -1,48 +0,0 @@
1
- // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
-
3
- exports[`convertToPerplexityMessages > assistant messages > should convert an assistant message with text content 1`] = `
4
- [
5
- {
6
- "content": "Assistant reply",
7
- "role": "assistant",
8
- },
9
- ]
10
- `;
11
-
12
- exports[`convertToPerplexityMessages > system messages > should convert a system message with text content 1`] = `
13
- [
14
- {
15
- "content": "System initialization",
16
- "role": "system",
17
- },
18
- ]
19
- `;
20
-
21
- exports[`convertToPerplexityMessages > user messages > should convert a user message with image parts 1`] = `
22
- [
23
- {
24
- "content": [
25
- {
26
- "text": "Hello ",
27
- "type": "text",
28
- },
29
- {
30
- "image_url": {
31
- "url": "data:image/png;base64,AAECAw==",
32
- },
33
- "type": "image_url",
34
- },
35
- ],
36
- "role": "user",
37
- },
38
- ]
39
- `;
40
-
41
- exports[`convertToPerplexityMessages > user messages > should convert a user message with text parts 1`] = `
42
- [
43
- {
44
- "content": "Hello World",
45
- "role": "user",
46
- },
47
- ]
48
- `;
@@ -1,85 +0,0 @@
1
- import { convertToPerplexityMessages } from './convert-to-perplexity-messages';
2
- import { UnsupportedFunctionalityError } from '@ai-sdk/provider';
3
- import { describe, it, expect } from 'vitest';
4
-
5
- describe('convertToPerplexityMessages', () => {
6
- describe('system messages', () => {
7
- it('should convert a system message with text content', () => {
8
- expect(
9
- convertToPerplexityMessages([
10
- {
11
- role: 'system',
12
- content: 'System initialization',
13
- },
14
- ]),
15
- ).toMatchSnapshot();
16
- });
17
- });
18
-
19
- describe('user messages', () => {
20
- it('should convert a user message with text parts', () => {
21
- expect(
22
- convertToPerplexityMessages([
23
- {
24
- role: 'user',
25
- content: [
26
- { type: 'text', text: 'Hello ' },
27
- { type: 'text', text: 'World' },
28
- ],
29
- },
30
- ]),
31
- ).toMatchSnapshot();
32
- });
33
-
34
- it('should convert a user message with image parts', () => {
35
- expect(
36
- convertToPerplexityMessages([
37
- {
38
- role: 'user',
39
- content: [
40
- { type: 'text', text: 'Hello ' },
41
- {
42
- type: 'file',
43
- data: new Uint8Array([0, 1, 2, 3]),
44
- mediaType: 'image/png',
45
- },
46
- ],
47
- },
48
- ]),
49
- ).toMatchSnapshot();
50
- });
51
- });
52
-
53
- describe('assistant messages', () => {
54
- it('should convert an assistant message with text content', () => {
55
- expect(
56
- convertToPerplexityMessages([
57
- {
58
- role: 'assistant',
59
- content: [{ type: 'text', text: 'Assistant reply' }],
60
- },
61
- ]),
62
- ).toMatchSnapshot();
63
- });
64
- });
65
-
66
- describe('tool messages', () => {
67
- it('should throw an error for tool messages', () => {
68
- expect(() => {
69
- convertToPerplexityMessages([
70
- {
71
- role: 'tool',
72
- content: [
73
- {
74
- type: 'tool-result',
75
- toolCallId: 'dummy-tool-call-id',
76
- toolName: 'dummy-tool-name',
77
- output: { type: 'text', value: 'This should fail' },
78
- },
79
- ],
80
- },
81
- ]);
82
- }).toThrow(UnsupportedFunctionalityError);
83
- });
84
- });
85
- });
@@ -1,1101 +0,0 @@
1
- // TEST FILE DOES NOT USE THE PROVIDER `createPerplexity`
2
-
3
- import { describe, it, expect } from 'vitest';
4
- import { LanguageModelV3Prompt } from '@ai-sdk/provider';
5
- import { createTestServer } from '@ai-sdk/test-server/with-vitest';
6
- import {
7
- convertReadableStreamToArray,
8
- mockId,
9
- } from '@ai-sdk/provider-utils/test';
10
- import { z } from 'zod/v4';
11
- import {
12
- perplexityImageSchema,
13
- PerplexityLanguageModel,
14
- } from './perplexity-language-model';
15
-
16
- const TEST_PROMPT: LanguageModelV3Prompt = [
17
- { role: 'user', content: [{ type: 'text', text: 'Hello' }] },
18
- ];
19
-
20
- describe('PerplexityLanguageModel', () => {
21
- describe('doGenerate', () => {
22
- const modelId = 'perplexity-001';
23
-
24
- const perplexityModel = new PerplexityLanguageModel(modelId, {
25
- baseURL: 'https://api.perplexity.ai',
26
- headers: () => ({
27
- authorization: 'Bearer test-token',
28
- 'content-type': 'application/json',
29
- }),
30
- generateId: mockId(),
31
- });
32
-
33
- // Create a unified test server to handle JSON responses.
34
- const jsonServer = createTestServer({
35
- 'https://api.perplexity.ai/chat/completions': {
36
- response: {
37
- type: 'json-value',
38
- headers: { 'content-type': 'application/json' },
39
- body: {},
40
- },
41
- },
42
- });
43
-
44
- // Helper to prepare the JSON response for doGenerate.
45
- function prepareJsonResponse({
46
- content = '',
47
- usage = { prompt_tokens: 10, completion_tokens: 20 },
48
- id = 'test-id',
49
- created = 1680000000,
50
- model = modelId,
51
- headers = {},
52
- citations = [],
53
- images,
54
- }: {
55
- content?: string;
56
- usage?: {
57
- prompt_tokens: number;
58
- completion_tokens: number;
59
- citation_tokens?: number;
60
- num_search_queries?: number;
61
- reasoning_tokens?: number;
62
- };
63
- id?: string;
64
- created?: number;
65
- model?: string;
66
- headers?: Record<string, string>;
67
- citations?: string[];
68
- images?: z.infer<typeof perplexityImageSchema>[];
69
- } = {}) {
70
- jsonServer.urls['https://api.perplexity.ai/chat/completions'].response = {
71
- type: 'json-value',
72
- headers: { 'content-type': 'application/json', ...headers },
73
- body: {
74
- id,
75
- created,
76
- model,
77
- choices: [
78
- {
79
- message: {
80
- role: 'assistant',
81
- content,
82
- },
83
- finish_reason: 'stop',
84
- },
85
- ],
86
- citations,
87
- images,
88
- usage,
89
- },
90
- };
91
- }
92
-
93
- it('should extract content correctly', async () => {
94
- prepareJsonResponse({ content: 'Hello, World!' });
95
-
96
- const result = await perplexityModel.doGenerate({
97
- prompt: TEST_PROMPT,
98
- });
99
-
100
- expect(result.content).toMatchInlineSnapshot(`
101
- [
102
- {
103
- "text": "Hello, World!",
104
- "type": "text",
105
- },
106
- ]
107
- `);
108
-
109
- expect(result.usage).toMatchInlineSnapshot(`
110
- {
111
- "inputTokens": {
112
- "cacheRead": undefined,
113
- "cacheWrite": undefined,
114
- "noCache": 10,
115
- "total": 10,
116
- },
117
- "outputTokens": {
118
- "reasoning": 0,
119
- "text": 20,
120
- "total": 20,
121
- },
122
- "raw": {
123
- "completion_tokens": 20,
124
- "prompt_tokens": 10,
125
- },
126
- }
127
- `);
128
-
129
- expect({
130
- id: result.response?.id,
131
- timestamp: result.response?.timestamp,
132
- modelId: result.response?.modelId,
133
- }).toStrictEqual({
134
- id: 'test-id',
135
- timestamp: new Date(1680000000 * 1000),
136
- modelId,
137
- });
138
- });
139
-
140
- it('should send the correct request body', async () => {
141
- prepareJsonResponse({ content: '' });
142
- await perplexityModel.doGenerate({
143
- prompt: TEST_PROMPT,
144
- });
145
- expect(await jsonServer.calls[0].requestBodyJson).toEqual({
146
- model: modelId,
147
- messages: [{ role: 'user', content: 'Hello' }],
148
- });
149
- });
150
-
151
- it('should pass through perplexity provider options', async () => {
152
- prepareJsonResponse({ content: '' });
153
- await perplexityModel.doGenerate({
154
- prompt: TEST_PROMPT,
155
- providerOptions: {
156
- perplexity: {
157
- search_recency_filter: 'month',
158
- return_images: true,
159
- },
160
- },
161
- });
162
-
163
- expect(await jsonServer.calls[0].requestBodyJson).toEqual({
164
- model: modelId,
165
- messages: [{ role: 'user', content: 'Hello' }],
166
- search_recency_filter: 'month',
167
- return_images: true,
168
- });
169
- });
170
-
171
- it('should handle PDF files with base64 encoding', async () => {
172
- const mockPdfData = 'mock-pdf-data';
173
- const prompt: LanguageModelV3Prompt = [
174
- {
175
- role: 'user',
176
- content: [
177
- { type: 'text', text: 'Analyze this PDF' },
178
- {
179
- type: 'file',
180
- mediaType: 'application/pdf',
181
- data: mockPdfData,
182
- filename: 'test.pdf',
183
- },
184
- ],
185
- },
186
- ];
187
-
188
- prepareJsonResponse({
189
- content: 'This is an analysis of the PDF',
190
- });
191
-
192
- const result = await perplexityModel.doGenerate({ prompt });
193
-
194
- // Verify the request contains the correct PDF format
195
- const requestBody =
196
- await jsonServer.calls[jsonServer.calls.length - 1].requestBodyJson;
197
- expect(requestBody.messages[0].content).toEqual([
198
- {
199
- type: 'text',
200
- text: 'Analyze this PDF',
201
- },
202
- {
203
- type: 'file_url',
204
- file_url: {
205
- url: expect.stringContaining(mockPdfData),
206
- },
207
- file_name: 'test.pdf',
208
- },
209
- ]);
210
-
211
- // Verify the response is processed correctly
212
- expect(result.content).toEqual([
213
- {
214
- type: 'text',
215
- text: 'This is an analysis of the PDF',
216
- },
217
- ]);
218
- });
219
-
220
- it('should handle PDF files with URLs', async () => {
221
- const pdfUrl = 'https://example.com/test.pdf';
222
- const prompt: LanguageModelV3Prompt = [
223
- {
224
- role: 'user',
225
- content: [
226
- { type: 'text', text: 'Analyze this PDF' },
227
- {
228
- type: 'file',
229
- mediaType: 'application/pdf',
230
- data: new URL(pdfUrl),
231
- filename: 'test.pdf',
232
- },
233
- ],
234
- },
235
- ];
236
-
237
- prepareJsonResponse({
238
- content: 'This is an analysis of the PDF from URL',
239
- });
240
-
241
- const result = await perplexityModel.doGenerate({ prompt });
242
-
243
- // Verify the request contains the correct PDF URL format
244
- const requestBody =
245
- await jsonServer.calls[jsonServer.calls.length - 1].requestBodyJson;
246
- expect(requestBody.messages[0].content).toEqual([
247
- {
248
- type: 'text',
249
- text: 'Analyze this PDF',
250
- },
251
- {
252
- type: 'file_url',
253
- file_url: {
254
- url: pdfUrl,
255
- },
256
- file_name: 'test.pdf',
257
- },
258
- ]);
259
-
260
- // Verify the response is processed correctly
261
- expect(result.content).toEqual([
262
- {
263
- type: 'text',
264
- text: 'This is an analysis of the PDF from URL',
265
- },
266
- ]);
267
- });
268
-
269
- it('should extract citations as sources', async () => {
270
- prepareJsonResponse({
271
- citations: ['http://example.com/123', 'https://example.com/456'],
272
- });
273
-
274
- const result = await perplexityModel.doGenerate({
275
- prompt: TEST_PROMPT,
276
- });
277
-
278
- expect(result.content).toMatchInlineSnapshot(`
279
- [
280
- {
281
- "id": "id-0",
282
- "sourceType": "url",
283
- "type": "source",
284
- "url": "http://example.com/123",
285
- },
286
- {
287
- "id": "id-1",
288
- "sourceType": "url",
289
- "type": "source",
290
- "url": "https://example.com/456",
291
- },
292
- ]
293
- `);
294
- });
295
-
296
- it('should extract images', async () => {
297
- prepareJsonResponse({
298
- images: [
299
- {
300
- image_url: 'https://example.com/image.jpg',
301
- origin_url: 'https://example.com/image.jpg',
302
- height: 100,
303
- width: 100,
304
- },
305
- ],
306
- });
307
-
308
- const result = await perplexityModel.doGenerate({
309
- prompt: TEST_PROMPT,
310
- });
311
-
312
- expect(result.providerMetadata).toStrictEqual({
313
- perplexity: {
314
- images: [
315
- {
316
- imageUrl: 'https://example.com/image.jpg',
317
- originUrl: 'https://example.com/image.jpg',
318
- height: 100,
319
- width: 100,
320
- },
321
- ],
322
- usage: {
323
- citationTokens: null,
324
- numSearchQueries: null,
325
- },
326
- },
327
- });
328
- });
329
-
330
- it('should extract usage', async () => {
331
- prepareJsonResponse({
332
- usage: {
333
- prompt_tokens: 10,
334
- completion_tokens: 20,
335
- citation_tokens: 30,
336
- num_search_queries: 40,
337
- reasoning_tokens: 50,
338
- },
339
- });
340
-
341
- const result = await perplexityModel.doGenerate({
342
- prompt: TEST_PROMPT,
343
- });
344
-
345
- expect(result.usage).toMatchInlineSnapshot(`
346
- {
347
- "inputTokens": {
348
- "cacheRead": undefined,
349
- "cacheWrite": undefined,
350
- "noCache": 10,
351
- "total": 10,
352
- },
353
- "outputTokens": {
354
- "reasoning": 50,
355
- "text": -30,
356
- "total": 20,
357
- },
358
- "raw": {
359
- "citation_tokens": 30,
360
- "completion_tokens": 20,
361
- "num_search_queries": 40,
362
- "prompt_tokens": 10,
363
- "reasoning_tokens": 50,
364
- },
365
- }
366
- `);
367
- expect(result.providerMetadata).toMatchInlineSnapshot(
368
- `
369
- {
370
- "perplexity": {
371
- "images": null,
372
- "usage": {
373
- "citationTokens": 30,
374
- "numSearchQueries": 40,
375
- },
376
- },
377
- }
378
- `,
379
- );
380
- });
381
-
382
- it('should pass headers from provider and request', async () => {
383
- prepareJsonResponse({ content: '' });
384
- const lmWithCustomHeaders = new PerplexityLanguageModel(modelId, {
385
- baseURL: 'https://api.perplexity.ai',
386
- headers: () => ({
387
- authorization: 'Bearer test-api-key',
388
- 'Custom-Provider-Header': 'provider-header-value',
389
- }),
390
- generateId: mockId(),
391
- });
392
-
393
- await lmWithCustomHeaders.doGenerate({
394
- prompt: TEST_PROMPT,
395
- headers: { 'Custom-Request-Header': 'request-header-value' },
396
- });
397
-
398
- expect(jsonServer.calls[0].requestHeaders).toEqual({
399
- authorization: 'Bearer test-api-key',
400
- 'content-type': 'application/json',
401
- 'custom-provider-header': 'provider-header-value',
402
- 'custom-request-header': 'request-header-value',
403
- });
404
- });
405
- });
406
-
407
- describe('doStream', () => {
408
- const modelId = 'perplexity-001';
409
-
410
- const streamServer = createTestServer({
411
- 'https://api.perplexity.ai/chat/completions': {
412
- response: {
413
- type: 'stream-chunks',
414
- headers: {
415
- 'content-type': 'text/event-stream',
416
- 'cache-control': 'no-cache',
417
- connection: 'keep-alive',
418
- },
419
- chunks: [],
420
- },
421
- },
422
- });
423
-
424
- const perplexityLM = new PerplexityLanguageModel(modelId, {
425
- baseURL: 'https://api.perplexity.ai',
426
- headers: () => ({ authorization: 'Bearer test-token' }),
427
- generateId: mockId(),
428
- });
429
-
430
- // Helper to prepare the stream response.
431
- function prepareStreamResponse({
432
- contents,
433
- usage = { prompt_tokens: 10, completion_tokens: 20 },
434
- citations = [],
435
- images,
436
- }: {
437
- contents: string[];
438
- usage?: {
439
- prompt_tokens: number;
440
- completion_tokens: number;
441
- citation_tokens?: number;
442
- num_search_queries?: number;
443
- reasoning_tokens?: number;
444
- };
445
- citations?: string[];
446
- images?: z.infer<typeof perplexityImageSchema>[];
447
- }) {
448
- const baseChunk = (
449
- content: string,
450
- finish_reason: string | null = null,
451
- includeUsage = false,
452
- ) => {
453
- const chunkObj: any = {
454
- id: 'stream-id',
455
- created: 1680003600,
456
- model: modelId,
457
- images,
458
- citations,
459
- choices: [
460
- {
461
- delta: { role: 'assistant', content },
462
- finish_reason,
463
- },
464
- ],
465
- };
466
- if (includeUsage) {
467
- chunkObj.usage = usage;
468
- }
469
- return `data: ${JSON.stringify(chunkObj)}\n\n`;
470
- };
471
-
472
- streamServer.urls['https://api.perplexity.ai/chat/completions'].response =
473
- {
474
- type: 'stream-chunks',
475
- headers: {
476
- 'content-type': 'text/event-stream',
477
- 'cache-control': 'no-cache',
478
- connection: 'keep-alive',
479
- },
480
- chunks: [
481
- ...contents.slice(0, -1).map(text => baseChunk(text)),
482
- // Final chunk: include finish_reason and usage.
483
- baseChunk(contents[contents.length - 1], 'stop', true),
484
- 'data: [DONE]\n\n',
485
- ],
486
- };
487
- }
488
-
489
- it('should stream text deltas', async () => {
490
- prepareStreamResponse({ contents: ['Hello', ', ', 'World!'] });
491
-
492
- const { stream } = await perplexityLM.doStream({
493
- prompt: TEST_PROMPT,
494
- includeRawChunks: false,
495
- });
496
-
497
- const result = await convertReadableStreamToArray(stream);
498
-
499
- expect(result).toMatchInlineSnapshot(`
500
- [
501
- {
502
- "type": "stream-start",
503
- "warnings": [],
504
- },
505
- {
506
- "id": "stream-id",
507
- "modelId": "perplexity-001",
508
- "timestamp": 2023-03-28T11:40:00.000Z,
509
- "type": "response-metadata",
510
- },
511
- {
512
- "id": "0",
513
- "type": "text-start",
514
- },
515
- {
516
- "delta": "Hello",
517
- "id": "0",
518
- "type": "text-delta",
519
- },
520
- {
521
- "delta": ", ",
522
- "id": "0",
523
- "type": "text-delta",
524
- },
525
- {
526
- "delta": "World!",
527
- "id": "0",
528
- "type": "text-delta",
529
- },
530
- {
531
- "id": "0",
532
- "type": "text-end",
533
- },
534
- {
535
- "finishReason": {
536
- "raw": "stop",
537
- "unified": "stop",
538
- },
539
- "providerMetadata": {
540
- "perplexity": {
541
- "images": null,
542
- "usage": {
543
- "citationTokens": null,
544
- "numSearchQueries": null,
545
- },
546
- },
547
- },
548
- "type": "finish",
549
- "usage": {
550
- "inputTokens": {
551
- "cacheRead": undefined,
552
- "cacheWrite": undefined,
553
- "noCache": 10,
554
- "total": 10,
555
- },
556
- "outputTokens": {
557
- "reasoning": 0,
558
- "text": 20,
559
- "total": 20,
560
- },
561
- "raw": {
562
- "completion_tokens": 20,
563
- "prompt_tokens": 10,
564
- },
565
- },
566
- },
567
- ]
568
- `);
569
- });
570
-
571
- it('should stream sources', async () => {
572
- prepareStreamResponse({
573
- contents: ['Hello', ', ', 'World!'],
574
- citations: ['http://example.com/123', 'https://example.com/456'],
575
- });
576
-
577
- const { stream } = await perplexityLM.doStream({
578
- prompt: TEST_PROMPT,
579
- includeRawChunks: false,
580
- });
581
-
582
- const result = await convertReadableStreamToArray(stream);
583
-
584
- expect(result).toMatchInlineSnapshot(`
585
- [
586
- {
587
- "type": "stream-start",
588
- "warnings": [],
589
- },
590
- {
591
- "id": "stream-id",
592
- "modelId": "perplexity-001",
593
- "timestamp": 2023-03-28T11:40:00.000Z,
594
- "type": "response-metadata",
595
- },
596
- {
597
- "id": "id-0",
598
- "sourceType": "url",
599
- "type": "source",
600
- "url": "http://example.com/123",
601
- },
602
- {
603
- "id": "id-1",
604
- "sourceType": "url",
605
- "type": "source",
606
- "url": "https://example.com/456",
607
- },
608
- {
609
- "id": "0",
610
- "type": "text-start",
611
- },
612
- {
613
- "delta": "Hello",
614
- "id": "0",
615
- "type": "text-delta",
616
- },
617
- {
618
- "delta": ", ",
619
- "id": "0",
620
- "type": "text-delta",
621
- },
622
- {
623
- "delta": "World!",
624
- "id": "0",
625
- "type": "text-delta",
626
- },
627
- {
628
- "id": "0",
629
- "type": "text-end",
630
- },
631
- {
632
- "finishReason": {
633
- "raw": "stop",
634
- "unified": "stop",
635
- },
636
- "providerMetadata": {
637
- "perplexity": {
638
- "images": null,
639
- "usage": {
640
- "citationTokens": null,
641
- "numSearchQueries": null,
642
- },
643
- },
644
- },
645
- "type": "finish",
646
- "usage": {
647
- "inputTokens": {
648
- "cacheRead": undefined,
649
- "cacheWrite": undefined,
650
- "noCache": 10,
651
- "total": 10,
652
- },
653
- "outputTokens": {
654
- "reasoning": 0,
655
- "text": 20,
656
- "total": 20,
657
- },
658
- "raw": {
659
- "completion_tokens": 20,
660
- "prompt_tokens": 10,
661
- },
662
- },
663
- },
664
- ]
665
- `);
666
- });
667
-
668
- it('should send the correct streaming request body', async () => {
669
- prepareStreamResponse({ contents: [] });
670
-
671
- await perplexityLM.doStream({
672
- prompt: TEST_PROMPT,
673
- includeRawChunks: false,
674
- });
675
-
676
- expect(await streamServer.calls[0].requestBodyJson).toEqual({
677
- model: modelId,
678
- messages: [{ role: 'user', content: 'Hello' }],
679
- stream: true,
680
- });
681
- });
682
-
683
- it('should send usage', async () => {
684
- prepareStreamResponse({
685
- contents: ['Hello', ', ', 'World!'],
686
- images: [
687
- {
688
- image_url: 'https://example.com/image.jpg',
689
- origin_url: 'https://example.com/image.jpg',
690
- height: 100,
691
- width: 100,
692
- },
693
- ],
694
- });
695
- const { stream } = await perplexityLM.doStream({
696
- prompt: TEST_PROMPT,
697
- includeRawChunks: false,
698
- });
699
-
700
- const result = await convertReadableStreamToArray(stream);
701
-
702
- expect(result).toMatchInlineSnapshot(`
703
- [
704
- {
705
- "type": "stream-start",
706
- "warnings": [],
707
- },
708
- {
709
- "id": "stream-id",
710
- "modelId": "perplexity-001",
711
- "timestamp": 2023-03-28T11:40:00.000Z,
712
- "type": "response-metadata",
713
- },
714
- {
715
- "id": "0",
716
- "type": "text-start",
717
- },
718
- {
719
- "delta": "Hello",
720
- "id": "0",
721
- "type": "text-delta",
722
- },
723
- {
724
- "delta": ", ",
725
- "id": "0",
726
- "type": "text-delta",
727
- },
728
- {
729
- "delta": "World!",
730
- "id": "0",
731
- "type": "text-delta",
732
- },
733
- {
734
- "id": "0",
735
- "type": "text-end",
736
- },
737
- {
738
- "finishReason": {
739
- "raw": "stop",
740
- "unified": "stop",
741
- },
742
- "providerMetadata": {
743
- "perplexity": {
744
- "images": [
745
- {
746
- "height": 100,
747
- "imageUrl": "https://example.com/image.jpg",
748
- "originUrl": "https://example.com/image.jpg",
749
- "width": 100,
750
- },
751
- ],
752
- "usage": {
753
- "citationTokens": null,
754
- "numSearchQueries": null,
755
- },
756
- },
757
- },
758
- "type": "finish",
759
- "usage": {
760
- "inputTokens": {
761
- "cacheRead": undefined,
762
- "cacheWrite": undefined,
763
- "noCache": 10,
764
- "total": 10,
765
- },
766
- "outputTokens": {
767
- "reasoning": 0,
768
- "text": 20,
769
- "total": 20,
770
- },
771
- "raw": {
772
- "completion_tokens": 20,
773
- "prompt_tokens": 10,
774
- },
775
- },
776
- },
777
- ]
778
- `);
779
- });
780
-
781
- it('should send images', async () => {
782
- prepareStreamResponse({
783
- contents: ['Hello', ', ', 'World!'],
784
- usage: {
785
- prompt_tokens: 11,
786
- completion_tokens: 21,
787
- citation_tokens: 30,
788
- num_search_queries: 40,
789
- reasoning_tokens: 50,
790
- },
791
- });
792
-
793
- const { stream } = await perplexityLM.doStream({
794
- prompt: TEST_PROMPT,
795
- includeRawChunks: false,
796
- });
797
-
798
- const result = await convertReadableStreamToArray(stream);
799
-
800
- expect(result).toMatchInlineSnapshot(`
801
- [
802
- {
803
- "type": "stream-start",
804
- "warnings": [],
805
- },
806
- {
807
- "id": "stream-id",
808
- "modelId": "perplexity-001",
809
- "timestamp": 2023-03-28T11:40:00.000Z,
810
- "type": "response-metadata",
811
- },
812
- {
813
- "id": "0",
814
- "type": "text-start",
815
- },
816
- {
817
- "delta": "Hello",
818
- "id": "0",
819
- "type": "text-delta",
820
- },
821
- {
822
- "delta": ", ",
823
- "id": "0",
824
- "type": "text-delta",
825
- },
826
- {
827
- "delta": "World!",
828
- "id": "0",
829
- "type": "text-delta",
830
- },
831
- {
832
- "id": "0",
833
- "type": "text-end",
834
- },
835
- {
836
- "finishReason": {
837
- "raw": "stop",
838
- "unified": "stop",
839
- },
840
- "providerMetadata": {
841
- "perplexity": {
842
- "images": null,
843
- "usage": {
844
- "citationTokens": 30,
845
- "numSearchQueries": 40,
846
- },
847
- },
848
- },
849
- "type": "finish",
850
- "usage": {
851
- "inputTokens": {
852
- "cacheRead": undefined,
853
- "cacheWrite": undefined,
854
- "noCache": 11,
855
- "total": 11,
856
- },
857
- "outputTokens": {
858
- "reasoning": 50,
859
- "text": -29,
860
- "total": 21,
861
- },
862
- "raw": {
863
- "citation_tokens": 30,
864
- "completion_tokens": 21,
865
- "num_search_queries": 40,
866
- "prompt_tokens": 11,
867
- "reasoning_tokens": 50,
868
- },
869
- },
870
- },
871
- ]
872
- `);
873
- });
874
-
875
- it('should pass headers', async () => {
876
- prepareStreamResponse({ contents: [] });
877
- const lmWithCustomHeaders = new PerplexityLanguageModel(modelId, {
878
- baseURL: 'https://api.perplexity.ai',
879
- headers: () => ({
880
- authorization: 'Bearer test-api-key',
881
- 'Custom-Provider-Header': 'provider-header-value',
882
- }),
883
- generateId: mockId(),
884
- });
885
-
886
- await lmWithCustomHeaders.doStream({
887
- prompt: TEST_PROMPT,
888
- includeRawChunks: false,
889
- headers: { 'Custom-Request-Header': 'request-header-value' },
890
- });
891
-
892
- expect(streamServer.calls[0].requestHeaders).toEqual({
893
- authorization: 'Bearer test-api-key',
894
- 'content-type': 'application/json',
895
- 'custom-provider-header': 'provider-header-value',
896
- 'custom-request-header': 'request-header-value',
897
- });
898
- });
899
-
900
- it('should stream raw chunks when includeRawChunks is true', async () => {
901
- streamServer.urls['https://api.perplexity.ai/chat/completions'].response =
902
- {
903
- type: 'stream-chunks',
904
- headers: {
905
- 'content-type': 'text/event-stream',
906
- 'cache-control': 'no-cache',
907
- connection: 'keep-alive',
908
- },
909
- chunks: [
910
- `data: {"id":"ppl-123","object":"chat.completion.chunk","created":1234567890,"model":"perplexity-001","choices":[{"index":0,"delta":{"role":"assistant","content":"Hello"},"finish_reason":null}],"citations":["https://example.com"]}\n\n`,
911
- `data: {"id":"ppl-456","object":"chat.completion.chunk","created":1234567890,"model":"perplexity-001","choices":[{"index":0,"delta":{"content":" world"},"finish_reason":null}]}\n\n`,
912
- `data: {"id":"ppl-789","object":"chat.completion.chunk","created":1234567890,"model":"perplexity-001","choices":[{"index":0,"delta":{},"finish_reason":"stop"}],"usage":{"prompt_tokens":10,"completion_tokens":5,"total_tokens":15,"citation_tokens":2,"num_search_queries":1}}\n\n`,
913
- 'data: [DONE]\n\n',
914
- ],
915
- };
916
-
917
- const { stream } = await perplexityLM.doStream({
918
- prompt: TEST_PROMPT,
919
- includeRawChunks: true,
920
- });
921
-
922
- const chunks = await convertReadableStreamToArray(stream);
923
-
924
- expect(chunks).toMatchInlineSnapshot(`
925
- [
926
- {
927
- "type": "stream-start",
928
- "warnings": [],
929
- },
930
- {
931
- "rawValue": {
932
- "choices": [
933
- {
934
- "delta": {
935
- "content": "Hello",
936
- "role": "assistant",
937
- },
938
- "finish_reason": null,
939
- "index": 0,
940
- },
941
- ],
942
- "citations": [
943
- "https://example.com",
944
- ],
945
- "created": 1234567890,
946
- "id": "ppl-123",
947
- "model": "perplexity-001",
948
- "object": "chat.completion.chunk",
949
- },
950
- "type": "raw",
951
- },
952
- {
953
- "id": "ppl-123",
954
- "modelId": "perplexity-001",
955
- "timestamp": 2009-02-13T23:31:30.000Z,
956
- "type": "response-metadata",
957
- },
958
- {
959
- "id": "id-2",
960
- "sourceType": "url",
961
- "type": "source",
962
- "url": "https://example.com",
963
- },
964
- {
965
- "id": "0",
966
- "type": "text-start",
967
- },
968
- {
969
- "delta": "Hello",
970
- "id": "0",
971
- "type": "text-delta",
972
- },
973
- {
974
- "rawValue": {
975
- "choices": [
976
- {
977
- "delta": {
978
- "content": " world",
979
- },
980
- "finish_reason": null,
981
- "index": 0,
982
- },
983
- ],
984
- "created": 1234567890,
985
- "id": "ppl-456",
986
- "model": "perplexity-001",
987
- "object": "chat.completion.chunk",
988
- },
989
- "type": "raw",
990
- },
991
- {
992
- "error": [AI_TypeValidationError: Type validation failed: Value: {"id":"ppl-456","object":"chat.completion.chunk","created":1234567890,"model":"perplexity-001","choices":[{"index":0,"delta":{"content":" world"},"finish_reason":null}]}.
993
- Error message: [
994
- {
995
- "code": "invalid_value",
996
- "values": [
997
- "assistant"
998
- ],
999
- "path": [
1000
- "choices",
1001
- 0,
1002
- "delta",
1003
- "role"
1004
- ],
1005
- "message": "Invalid input: expected \\"assistant\\""
1006
- }
1007
- ]],
1008
- "type": "error",
1009
- },
1010
- {
1011
- "rawValue": {
1012
- "choices": [
1013
- {
1014
- "delta": {},
1015
- "finish_reason": "stop",
1016
- "index": 0,
1017
- },
1018
- ],
1019
- "created": 1234567890,
1020
- "id": "ppl-789",
1021
- "model": "perplexity-001",
1022
- "object": "chat.completion.chunk",
1023
- "usage": {
1024
- "citation_tokens": 2,
1025
- "completion_tokens": 5,
1026
- "num_search_queries": 1,
1027
- "prompt_tokens": 10,
1028
- "total_tokens": 15,
1029
- },
1030
- },
1031
- "type": "raw",
1032
- },
1033
- {
1034
- "error": [AI_TypeValidationError: Type validation failed: Value: {"id":"ppl-789","object":"chat.completion.chunk","created":1234567890,"model":"perplexity-001","choices":[{"index":0,"delta":{},"finish_reason":"stop"}],"usage":{"prompt_tokens":10,"completion_tokens":5,"total_tokens":15,"citation_tokens":2,"num_search_queries":1}}.
1035
- Error message: [
1036
- {
1037
- "code": "invalid_value",
1038
- "values": [
1039
- "assistant"
1040
- ],
1041
- "path": [
1042
- "choices",
1043
- 0,
1044
- "delta",
1045
- "role"
1046
- ],
1047
- "message": "Invalid input: expected \\"assistant\\""
1048
- },
1049
- {
1050
- "expected": "string",
1051
- "code": "invalid_type",
1052
- "path": [
1053
- "choices",
1054
- 0,
1055
- "delta",
1056
- "content"
1057
- ],
1058
- "message": "Invalid input: expected string, received undefined"
1059
- }
1060
- ]],
1061
- "type": "error",
1062
- },
1063
- {
1064
- "id": "0",
1065
- "type": "text-end",
1066
- },
1067
- {
1068
- "finishReason": {
1069
- "raw": undefined,
1070
- "unified": "other",
1071
- },
1072
- "providerMetadata": {
1073
- "perplexity": {
1074
- "images": null,
1075
- "usage": {
1076
- "citationTokens": null,
1077
- "numSearchQueries": null,
1078
- },
1079
- },
1080
- },
1081
- "type": "finish",
1082
- "usage": {
1083
- "inputTokens": {
1084
- "cacheRead": undefined,
1085
- "cacheWrite": undefined,
1086
- "noCache": undefined,
1087
- "total": undefined,
1088
- },
1089
- "outputTokens": {
1090
- "reasoning": undefined,
1091
- "text": undefined,
1092
- "total": undefined,
1093
- },
1094
- "raw": undefined,
1095
- },
1096
- },
1097
- ]
1098
- `);
1099
- });
1100
- });
1101
- });