diff --git a/docs/integrations/apis/openai-tasks.mdx b/docs/integrations/apis/openai-tasks.mdx new file mode 100644 index 00000000000..a4864dc959c --- /dev/null +++ b/docs/integrations/apis/openai-tasks.mdx @@ -0,0 +1,511 @@ +--- +title: OpenAI tasks +sidebarTitle: Tasks +--- + +Tasks are executed after the job is triggered and are the main building blocks of a job. You can string together as many tasks as you want. + +--- + +## All tasks + +### `createCompletion` + +Generates text completions as per given prompt. [Official OpenAI Docs](https://platform.openai.com/docs/api-reference/chat) + +```ts example.ts +run: async (payload, io, ctx) => { + // This code demonstrates using OpenAI's text completion with the "davinci" model. + // It generates text based on the given prompt. + await io.openai.createCompletion("completion", { + model: "davinci", + prompt: "Once upon a time", + }); +}, +``` + +### `backgroundCreateCompletion` + +Generates text completions in the background. [Official OpenAI Docs](https://platform.openai.com/docs/api-reference/chat) + +```ts example.ts +run: async (payload, io, ctx) => { + // This code showcases background text completion using the "gpt-3.5-turbo" model. + // It generates text based on the provided programming task and logs the result. + const programmingTask = `Create a function that checks if a string is a palindrome.`; + + const response = await io.openai.backgroundCreateCompletion("background-completion", { + model: "gpt-3.5-turbo", + prompt: `Coding task: ${programmingTask}\n\n`, + }); + + await io.logger.info("codeSnippet", response.choices[0]?.text); +}, +``` + +### `createChatCompletion` + +Generates text completions in a conversational context. [Official OpenAI Docs](https://platform.openai.com/docs/api-reference/chat) + +```ts example.ts +run: async (payload, io, ctx) => { + // This code demonstrates chat completion with the "gpt-3.5-turbo" model. + // It simulates a conversation by providing messages and receiving a chat response. + await io.openai.createChatCompletion("chat-completion", { + model: "gpt-3.5-turbo", + messages: [ + { + role: "user", + content: "Create a good programming joke about background jobs", + }, + ], + }); +}, +``` + +### `backgroundCreateChatCompletion` + +Generates text completions in a conversational context in the background. [Official OpenAI Docs](https://platform.openai.com/docs/api-reference/chat) + +```ts example.ts +run: async (payload, io, ctx) => { + // This code showcases background chat completion using the "gpt-3.5-turbo" model. + // It simulates a conversation with a user message and logs the response choices. + const response = await io.openai.backgroundCreateChatCompletion("background-chat-completion", { + model: "gpt-3.5-turbo", + messages: [ + { + role: "user", + content: "Create a good programming joke about background jobs", + }, + ], + }); + + await io.logger.info("choices", response.choices); +}, +``` + +### `retrieveModel` + +Retrieves a specific model by ID. [Official OpenAI Docs](https://platform.openai.com/docs/api-reference/models) + +```ts example.ts +run: async (payload, io, ctx) => { + // In this code snippet, we retrieve detailed information about a specific OpenAI model. + + // Specify the ID of the model you want to retrieve. Replace 'your_model_id' with the actual model ID. + const modelIdToRetrieve = "your_model_id"; + + try { + // Retrieve the model information using the OpenAI API + const retrievedModel = await io.openai.retrieveModel("get-model", { + model: modelIdToRetrieve, + }); + + // Log the detailed model information + await io.logger.info("retrievedModel", retrievedModel); + } catch (error) { + // Handle errors, such as if the model with the provided ID does not exist. + await io.logger.error("Error retrieving model:", error.message); + } +}, +``` + +### `listModels` + +Lists the available models. [Official OpenAI Docs](https://platform.openai.com/docs/api-reference/models) + +```ts example.ts +run: async (payload, io, ctx) => { + // This code lists available models without retrieving detailed information. + const models = await io.openai.listModels("list-models"); +}, +``` + +### `createEdit` + +Edits a given text prompt. [Official OpenAI Docs](https://platform.openai.com/docs/api-reference/edits) + +```ts example.ts +run: async (payload, io, ctx) => { + // This code snippet demonstrates using the OpenAI API to create an edit task. + + // Specify the task parameters: + const editTaskParams = { + model: "text-davinci-edit-001", // Replace with the desired model + input: "Thsi is ridddled with erors", // Replace with the input text + instruction: "Fix the spelling errors", // Replace with the editing instruction + }; + + try { + // Create an edit task using the OpenAI API + const editResponse = await io.openai.createEdit("edit", editTaskParams); + + // Log the response + await io.logger.info("editResponse", editResponse); + } catch (error) { + // Handle any potential errors that may occur during the API request. + await io.logger.error("Error creating edit task:", error.message); + } +}, +``` + +### `createImage` + +Generates images from textual descriptions. [Official OpenAI Docs](https://platform.openai.com/docs/api-reference/images) + +```ts example.ts + run: async (payload, io, ctx) => { + const imageResults = await io.openai.createImage("image", { + prompt: "A hedgehog wearing a party hat", + n: 2, + size: "256x256", + response_format: "url", + }); +``` + +### `createImageEdit` + +Creates an edited or extended image given an original image and a prompt. [Official OpenAI Docs](https://platform.openai.com/docs/api-reference/images) + +```ts example.ts +run: async (payload, io, ctx) => { + // Specify the parameters for the image edit + const imageEditParams = { + style: "data:image/png;base64,base64_encoded_style_image", + content: "data:image/png;base64,base64_encoded_content_image", + }; + + // Create the image edit using the OpenAI API + const imageEditResponse = await io.openai.createImageEdit(imageEditParams); + + // Log the response + await io.logger.info("imageEditResponse", imageEditResponse); +}, +``` + +### `createImageVariation` + +Creates a variation of a given image. [Official OpenAI Docs](https://platform.openai.com/docs/api-reference/images) + +```ts example.ts + run: async (payload, io, ctx) => { + // Specify the parameters for creating an image variation + const imageVariationParams = { + image: "data:image/png;base64,base64_encoded_image", + variation: "brightness(1.2) contrast(0.8) rotate(45deg)", + }; + + // Create the image variation using the OpenAI API + const imageVariationResponse = await io.openai.createImageVariation(imageVariationParams); + + // Log the response + await io.logger.info("imageVariationResponse", imageVariationResponse); + }, +``` + +### `createEmbedding` + +Generates embeddings for a given text. [Official OpenAI Docs](https://platform.openai.com/docs/api-reference/embeddings/object) + +```ts example.ts +run: async (payload, io, ctx) => { + // This code snippet demonstrates using the OpenAI API to create a text embedding. + + // Specify the task parameters: + const embeddingTaskParams = { + model: "text-embedding-ada-002", // Replace with the desired model + input: "The food was delicious and the waiter...", // Replace with the input text + }; + + try { + // Create a text embedding using the OpenAI API + const embeddingResponse = await io.openai.createEmbedding("embedding", embeddingTaskParams); + + // Log the response + await io.logger.info("embeddingResponse", embeddingResponse); + } catch (error) { + // Handle any potential errors that may occur during the API request. + await io.logger.error("Error creating text embedding:", error.message); + } +}, +``` + +### `createFile` + +Uploads a file to the OpenAI API. [Official OpenAI Docs](https://platform.openai.com/docs/api-reference/files/object) + +```ts example.ts +run: async (payload, io, ctx) => { + // Specify the parameters for creating a file + const fileParams = { + name: "example.txt", + content: "This is the content of the file.", + }; + + // Create the file using the OpenAI API + const fileResponse = await io.openai.createFile(fileParams); + + // Log the response + await io.logger.info("fileResponse", fileResponse); +}, +``` + +### `listFiles` + +Lists the uploaded files. [Official OpenAI Docs](https://platform.openai.com/docs/api-reference/files/object) + +```ts example.ts +run: async (payload, io, ctx) => { + // List the files available in your OpenAI account + const fileListResponse = await io.openai.listFiles(); + + // Log the list of files + await io.logger.info("fileListResponse", fileListResponse); +}, +``` + +### `createFineTuneFile` + +Uploads a file for fine-tuning a model. [Official OpenAI Docs](https://platform.openai.com/docs/api-reference/fine-tuning) + +```ts example.ts +run: async (payload, io, ctx) => { + // Specify the parameters for creating a fine-tune file + const fineTuneFileParams = { + model: "text-davinci-002", + prompt: "Translate English to French: 'Hello, world.'", + language: "en", + description: "Fine-tune file for translation task", + }; + + // Create the fine-tune file using the OpenAI API + const fineTuneFileResponse = await io.openai.createFineTuneFile(fineTuneFileParams); + + // Log the response + await io.logger.info("fineTuneFileResponse", fineTuneFileResponse); +}, +``` + +### `createFineTune` + +Fine-tunes a model on a given task. [Official OpenAI Docs](https://platform.openai.com/docs/api-reference/fine-tuning) + +```ts example.ts +run: async (payload, io, ctx) => { + // Specify the parameters for creating a fine-tune task + const fineTuneParams = { + model: "text-davinci-002", + dataset: "your_dataset_id", + description: "Fine-tune task for custom dataset", + }; + + // Create the fine-tune task using the OpenAI API + const fineTuneResponse = await io.openai.createFineTune(fineTuneParams); + + // Log the response + await io.logger.info("fineTuneResponse", fineTuneResponse); +}, +``` + +### `listFineTunes` + +Lists the available fine-tunes. [Official OpenAI Docs](https://platform.openai.com/docs/api-reference/fine-tuning) + +```ts example.ts +run: async (payload, io, ctx) => { + // List the fine-tunes available in your OpenAI account + const fineTunesListResponse = await io.openai.listFineTunes(); + + // Log the list of fine-tunes + await io.logger.info("fineTunesListResponse", fineTunesListResponse); +}, +``` + +### `retrieveFineTune` + +Retrieves a specific fine-tune by ID. [Official OpenAI Docs](https://platform.openai.com/docs/api-reference/fine-tuning) + +```ts example.ts +run: async (payload, io, ctx) => { + // Specify the ID of the fine-tune you want to retrieve + const fineTuneId = "your_fine_tune_id"; // Replace with the actual fine-tune ID + + // Retrieve the fine-tune using the OpenAI API + const retrievedFineTune = await io.openai.retrieveFineTune(fineTuneId); + + // Log the retrieved fine-tune + await io.logger.info("retrievedFineTune", retrievedFineTune); +}, +``` + +### `cancelFineTune` + +Cancels a specific fine-tune by ID. [Official OpenAI Docs](https://platform.openai.com/docs/api-reference/fine-tuning) + +```ts example.ts +run: async (payload, io, ctx) => { + // Specify the ID of the fine-tune you want to cancel + const fineTuneIdToCancel = "your_fine_tune_id"; // Replace with the actual fine-tune ID + + // Cancel the specified fine-tune using the OpenAI API + const cancellationResponse = await io.openai.cancelFineTune(fineTuneIdToCancel); + + // Log the cancellation response + await io.logger.info("cancellationResponse", cancellationResponse); +}, +``` + +### `createFineTuningJob` + +Creates a job that fine-tunes a specified model from a given dataset. [Official OpenAI Docs](https://platform.openai.com/docs/api-reference/fine-tuning) + +```ts example.ts +run: async (payload, io, ctx) => { + // Specify the parameters for creating a fine-tuning job + const fineTuningJobParams = { + fineTuneId: "your_fine_tune_id", // Replace with the actual fine-tune ID + datasetId: "your_dataset_id", // Replace with the ID of your dataset + model: "text-davinci-002", // Replace with the model for fine-tuning + n_examples: 100, // Replace with the number of examples + }; + + // Create the fine-tuning job using the OpenAI API + const fineTuningJobResponse = await io.openai.createFineTuningJob(fineTuningJobParams); + + // Log the response + await io.logger.info("fineTuningJobResponse", fineTuningJobResponse); +}, +``` + +### `retrieveFineTuningJob` + +Get info about a fine-tuning job. [Official OpenAI Docs](https://platform.openai.com/docs/api-reference/fine-tuning) + +```ts example.ts +run: async (payload, io, ctx) => { + // Specify the ID of the fine-tuning job you want to retrieve + const fineTuningJobId = "your_fine_tuning_job_id"; // Replace with the actual job ID + + // Retrieve the fine-tuning job using the OpenAI API + const retrievedJob = await io.openai.retrieveFineTuningJob(fineTuningJobId); + + // Log the retrieved job + await io.logger.info("retrievedJob", retrievedJob); +}, +``` + +### `cancelFineTuningJob` + +Cancel a fine-tuning job. [Official OpenAI Docs](https://platform.openai.com/docs/api-reference/fine-tuning) + +```ts example.ts +run: async (payload, io, ctx) => { + // Specify the ID of the fine-tuning job you want to cancel + const fineTuningJobIdToCancel = "your_fine_tuning_job_id"; // Replace with the actual job ID + + // Cancel the specified fine-tuning job using the OpenAI API + const cancellationResponse = await io.openai.cancelFineTuningJob(fineTuningJobIdToCancel); + + // Log the cancellation response + await io.logger.info("cancellationResponse", cancellationResponse); +}, +``` + +### `listFineTuningJobEvents` + +List events for a fine-tuning job. [Official OpenAI Docs](https://platform.openai.com/docs/api-reference/fine-tuning) + +```ts example.ts +run: async (payload, io, ctx) => { + // Specify the ID of the fine-tuning job for which you want to list events + const fineTuningJobId = "your_fine_tuning_job_id"; // Replace with the actual job ID + + // List events for the specified fine-tuning job using the OpenAI API + const eventsListResponse = await io.openai.listFineTuningJobEvents(fineTuningJobId); + + // Log the list of events + await io.logger.info("eventsListResponse", eventsListResponse); +}, +``` + +### `listFineTuningJobs` + +List fine tuning jobs. [Official OpenAI Docs](https://platform.openai.com/docs/api-reference/fine-tuning) + +```ts example.ts +run: async (payload, io, ctx) => { + // List the fine-tuning jobs available in your OpenAI account + const jobsListResponse = await io.openai.listFineTuningJobs(); + + // Log the list of fine-tuning jobs + await io.logger.info("jobsListResponse", jobsListResponse); +}, +``` + +## Example usage + +In this example we'll create a task that generates a random joke using OpenAI GPT 3.5 . + +```ts example.ts +import { TriggerClient, eventTrigger } from "@trigger.dev/sdk"; +import { OpenAI } from "@trigger.dev/openai"; +import { z } from "zod"; + +// Initialize a TriggerClient with the ID "jobs-showcase" +const client = new TriggerClient({ id: "jobs-showcase" }); + +// Create an instance of the OpenAI client and provide the OpenAI API key from environment variables +const openai = new OpenAI({ + id: "openai", + apiKey: process.env.OPENAI_API_KEY!, // Replace with your actual OpenAI API key +}); + +// Define a job that uses OpenAI GPT-3.5 Turbo to tell jokes +client.defineJob({ + id: "openai-tell-me-a-joke", + name: "OpenAI: tell me a joke", + version: "1.0.0", + trigger: eventTrigger({ + name: "openai.tasks", // Define the trigger event name + schema: z.object({ + jokePrompt: z.string(), // Expect a joke prompt as input + }), + }), + integrations: { + openai, // Use the OpenAI integration for this job + }, + run: async (payload, io, ctx) => { + // Retrieve information about the GPT-3.5 Turbo model + await io.openai.retrieveModel("get-model", { + model: "gpt-3.5-turbo", + }); + + // List available models (optional, for reference) + const models = await io.openai.listModels("list-models"); + + // Generate a joke in the background using the chat conversation format + const jokeResult = await io.openai.backgroundCreateChatCompletion( + "background-chat-completion", + { + model: "gpt-3.5-turbo", + messages: [ + { + role: "user", + content: payload.jokePrompt, // User-provided joke prompt + }, + ], + } + ); + + // Return the generated joke as the result + return { + joke: jokeResult.choices[0]?.message?.content, + }; + }, +}); + +// These lines are specific to the Express framework and can be removed if not needed +import { createExpressServer } from "@trigger.dev/express"; +createExpressServer(client); +``` diff --git a/docs/integrations/apis/openai.mdx b/docs/integrations/apis/openai.mdx index c78cf41c5d3..98924665797 100644 --- a/docs/integrations/apis/openai.mdx +++ b/docs/integrations/apis/openai.mdx @@ -1,17 +1,23 @@ --- -title: OpenAI +title: OpenAI overview & authentication +sidebarTitle: Overview & authentication --- +## Overview + Trigger.dev has a seamless integration with OpenAI, enabling developers to harness the power of AI language models in their serverless applications. With Trigger.dev's background tasks, long-running OpenAI completions become possible, even within the constraints of serverless timeouts. - - -## Installation + + Check out pre-built OpenAI jobs in our showcase. + -To get started with the OpenAI integration on Trigger.dev, you need to install the `@trigger.dev/openai` package. -You can do this using npm, pnpm, or yarn: +## Installing the OpenAI packages @@ -43,276 +49,12 @@ const openai = new OpenAI({ }); ``` -## Usage - -Include the OpenAI integration in your Trigger.dev job: - -```ts -client.defineJob({ - id: "openai-tasks", - name: "OpenAI Tasks", - version: "0.0.1", - trigger: eventTrigger({ - name: "openai.tasks", - schema: z.object({}), - }), - integrations: { - openai, - }, - run: async (payload, io, ctx) => { - // Now you can access the OpenAI tasks through the io object - await io.openai.createCompletion("completion", { - model: "davinci", - prompt: "Once upon a time", - }); - }, -}); -``` - ## Tasks -Tasks that are marked as "long-running" can last longer than your serverless timeout – they are performed on one of our background workers. - -| Function Name | Description | Long-running? | -| -------------------------------- | ------------------------------------------------------------------------- | ------------- | -| `createCompletion` | Generates text completions given a prompt. | -| `backgroundCreateCompletion` | Generates text completions in the background. | ✔ | -| `createChatCompletion` | Generates text completions in a conversational context. | -| `backgroundCreateChatCompletion` | Generates text completions in a conversational context in the background. | ✔ | -| `retrieveModel` | Retrieves a specific model by ID. | -| `listModels` | Lists the available models. | -| `createEdit` | Edits a given text prompt. | -| `createImage` | Generates images from textual descriptions. | -| `createImageEdit` | Creates an edited or extended image given an original image and a prompt | -| `createImageVariation` | Creates a variation of a given image. | -| `createEmbedding` | Generates embeddings for a given text. | -| `createFile` | Uploads a file to the OpenAI API. | -| `listFiles` | Lists the uploaded files. | -| `createFineTuneFile` | Uploads a file for fine-tuning a model. | -| `createFineTune` | Fine-tunes a model on a given task. | -| `listFineTunes` | Lists the available fine-tunes. | -| `retrieveFineTune` | Retrieves a specific fine-tune by ID. | -| `cancelFineTune` | Cancels a specific fine-tune by ID. | -| `createFineTuningJob` | Creates a job that fine-tunes a specified model from a given dataset. | -| `retrieveFineTuningJob` | Get info about a fine-tuning job. | -| `cancelFineTuningJob` | Cancel a fine-tuning job | -| `listFineTuningJobEvents` | List events for a fine-tuning job | -| `listFineTuningJobs` | List fine tuning jobs | - -## Examples - -### Generate a joke - -Here's an example of how to use the OpenAI integration in a Trigger.dev job. -In this example, we'll create a background task to generate a programming joke. - -```ts -client.defineJob({ - id: "openai-tasks", - name: "OpenAI Tasks", - version: "0.0.1", - trigger: eventTrigger({ - name: "openai.tasks", - schema: z.object({}), - }), - integrations: { - openai, - }, - run: async (payload, io, ctx) => { - const response = await io.openai.backgroundCreateChatCompletion("background-chat-completion", { - model: "gpt-3.5-turbo", - messages: [ - { - role: "user", - content: "Create a good programming joke about background jobs", - }, - ], - }); - - await io.logger.info("choices", response.choices); - }, -}); -``` - -### Generate Code Snippets - -In this example, we'll leverage Trigger.dev's background task to generate code snippets for -a given programming task: - -```ts -client.defineJob({ - id: "openai-tasks", - name: "OpenAI Tasks", - version: "0.0.1", - trigger: eventTrigger({ - name: "openai.tasks", - schema: z.object({}), - }), - integrations: { - openai, - }, - run: async (payload, io, ctx) => { - const programmingTask = `Create a function that checks if a string is a palindrome.`; - - const response = await io.openai.backgroundCreateCompletion("background-completion", { - model: "gpt-3.5-turbo", - prompt: `Coding task: ${programmingTask}\n\n`, - }); - - await io.logger.info("codeSnippet", response.choices[0]?.text); - }, -}); -``` - -### Summarize Text - -We'll use Trigger.dev's background task to summarize a lengthy article: +Once you have set up a OpenAI client, you can use it to create tasks. -```ts -client.defineJob({ - id: "openai-tasks", - name: "OpenAI Tasks", - version: "0.0.1", - trigger: eventTrigger({ - name: "openai.tasks", - schema: z.object({}), - }), - integrations: { - openai, - }, - run: async (payload, io, ctx) => { - const articleToSummarize = `Lorem ipsum. olor sit amet, consectetur adipiscing elit. - Sed nec aliquet sapien. Pellentesque vitae nisi id purus luctus tincidunt. - Proin condimentum malesuada turpis, eget tincidunt mauris viverra in.`; - - const response = await io.openai.backgroundCreateCompletion("background-completion", { - model: "gpt-3.5-turbo", - prompt: `Please summarize the following article:\n\n${articleToSummarize}`, - }); - - await io.logger.info("summary", response.choices[0]?.text); - }, -}); -``` - -### Draft Email Response - -we'll use Trigger.dev's background task to draft an email response based on a given email content: - -```ts -client.defineJob({ - id: "openai-tasks", - name: "OpenAI Tasks", - version: "0.0.1", - trigger: eventTrigger({ - name: "openai.tasks", - schema: z.object({}), - }), - integrations: { - openai, - }, - run: async (payload, io, ctx) => { - const emailContent = `Dear John, - - Thank you for your inquiry. We appreciate your interest in our products. - I have reviewed your request, and I'm pleased to inform you that we can - accommodate your requirements. Please find the attached proposal for your - reference. If you have any further questions, feel free to ask. - - Best regards, - Jane Doe`; - - const response = await io.openai.backgroundCreateChatCompletion("background-chat-completion", { - model: "gpt-3.5-turbo", - messages: [ - { - role: "user", - content: emailContent, - }, - { - role: "assistant", - content: "Draft a suitable response to the email above.", - }, - ], - }); - - await io.logger.info("draftedEmailResponse", response.choices[0]?.text); - }, -}); -``` - -### Chatbot Counseling Session - -This job represents a simulated AI counseling session. Leveraging OpenAI's ability to understand context and generate human-like text, it forms empathetic responses to user inputs. Such a system could be part of a mental wellness app. - -```ts -client.defineJob({ - id: "openai-chatbot-counseling", - name: "Chatbot Counseling Session", - version: "0.0.1", - trigger: eventTrigger({ - name: "openai.startCounselingSession", - schema: z.object({}), - }), - integrations: { - openai, - }, - run: async (payload, io, ctx) => { - const response = await io.openai.backgroundCreateChatCompletion( - "background-counseling-chat-completion", - { - model: "gpt-3.5-turbo", - messages: [ - { - role: "system", - content: "You are a helpful and empathetic AI counselor.", - }, - { - role: "user", - content: "I've been feeling really stressed out lately.", - }, - ], - } - ); - await io.logger.info("counseling session", response.choices); - }, -}); -``` - -### AI Roleplay Game Session - -This job creates a fantasy AI role-playing game. It could be fun for interactive storytelling or game development contexts. - -```ts -client.defineJob({ - id: "openai-roleplay-game-session", - name: "AI Roleplay Game Session", - version: "0.0.1", - trigger: eventTrigger({ - name: "openai.startRoleplayGameSession", - schema: z.object({}), - }), - integrations: { - openai, - }, - run: async (payload, io, ctx) => { - const response = await io.openai.backgroundCreateChatCompletion( - "background-roleplay-game-session-chat-completion", - { - model: "gpt-3.5-turbo", - messages: [ - { - role: "system", - content: "You are an intelligent guide in a fantasy role-playing game.", - }, - { - role: "user", - content: "I embark on a quest for the enchanted crown. What's the first step?", - }, - ], - } - ); - await io.logger.info("roleplay game session", response.choices); - }, -}); -``` + + + Perform different AI-powered tasks using OpenAI. + + diff --git a/docs/mint.json b/docs/mint.json index 597903b837d..054142f8b5a 100644 --- a/docs/mint.json +++ b/docs/mint.json @@ -255,7 +255,13 @@ ] }, "integrations/apis/linear", - "integrations/apis/openai", + { + "group": "OpenAI", + "pages": [ + "integrations/apis/openai", + "integrations/apis/openai-tasks" + ] + }, { "group": "Plain", "pages": [