I am working with Next.js 13.4.19
and I want to try OpenAI API. I have my API routes in good structure. When I hit the API route http://localhost:3000/api/course/createChapters
for POST
with:
{'title':'calculus','units':['algebra','differentiation','integration']}
I get the following error:
- error TypeError: Cannot read properties of undefined (reading 'headers') at eval (webpack-internal:///(rsc)/./node_modules/.pnpm/next@13.4.19_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/server/future/route-modules/app-route/module.js:266:61) at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
Here follows my gpt.ts
file:
import OpenAI from "openai";const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY,});interface OutputFormat { [key: string]: string | string[] | OutputFormat;}export async function strict_output( system_prompt: string, user_prompt: string | string[], output_format: OutputFormat, default_category: string = "", output_value_only: boolean = false, model: string = "gpt-3.5-turbo", temperature: number = 1, num_tries: number = 3, verbose: boolean = false) { const list_input: boolean = Array.isArray(user_prompt); const dynamic_elements: boolean = /<.*?>/.test(JSON.stringify(output_format)); const list_output: boolean = /\[.*?\]/.test(JSON.stringify(output_format)); let error_msg: string = ""; for (let i = 0; i < num_tries; i++) { let output_format_prompt: string = `\nYou are to output ${ list_output && "an array of objects in" } the following in json format: ${JSON.stringify( output_format )}. \nDo not put quotation marks or escape character \\ in the output fields.`; if (list_output) { output_format_prompt += `\nIf output field is a list, classify output into the best element of the list.`; } if (dynamic_elements) { output_format_prompt += `\nAny text enclosed by < and > indicates you must generate content to replace it. Example input: Go to <location>, Example output: Go to the garden\nAny output key containing < and > indicates you must generate the key name to replace it. Example input: {'<location>': 'description of location'}, Example output: {school: a place for education}`; } if (list_input) { output_format_prompt += `\nGenerate an array of json, one json for each input element.`; } const response = await openai.chat.completions.create({ temperature: temperature, model: model, messages: [ { role: "system", content: system_prompt + output_format_prompt + error_msg, }, { role: "user", content: user_prompt.toString() }, ], }); let res: string = response.choices[0].message?.content?.replace(/'/g, '"') ?? ""; res = res.replace(/(\w)"(\w)/g, "$1'$2"); if (verbose) { console.log("System prompt:", system_prompt + output_format_prompt + error_msg ); console.log("\nUser prompt:", user_prompt); console.log("\nGPT response:", res); } try { let output: any = JSON.parse(res); if (list_input) { if (!Array.isArray(output)) { throw new Error("Output format not in an array of json"); } } else { output = [output]; } for (let index = 0; index < output.length; index++) { for (const key in output_format) { if (/<.*?>/.test(key)) { continue; } if (!(key in output[index])) { throw new Error(`${key} not in json output`); } if (Array.isArray(output_format[key])) { const choices = output_format[key] as string[]; if (Array.isArray(output[index][key])) { output[index][key] = output[index][key][0]; } if (!choices.includes(output[index][key]) && default_category) { output[index][key] = default_category; } if (output[index][key].includes(":")) { output[index][key] = output[index][key].split(":")[0]; } } } if (output_value_only) { output[index] = Object.values(output[index]); if (output[index].length === 1) { output[index] = output[index][0]; } } } return list_input ? output : output[0]; } catch (e) { error_msg = `\n\nResult: ${res}\n\nError message: ${e}`; console.log("An exception occurred:", e); console.log("Current invalid json format ", res); } } return [];}
Here is my route.ts
file
import { strict_output } from "@/lib/gpt";import { createChaptersSchema } from "@/validators/course";import { NextResponse } from "next/server";import { ZodError } from "zod";export const POST = async (req: Request) => { try { const body = await req.json(); const { title, units } = createChaptersSchema.parse(body); type outptUnits = { title: string; chapters: { youtube_search_query: string; chapter_title: string; }[]; }; let output_units: outptUnits = await strict_output("You are an AI capable of curating course content, coming up with relevant chapter titles, and finding relevant youtube videos for each chapter", new Array(units.length).fill( `It is your job to create a course about ${title}. The user has requested to create chapters for each of the units. Then, for each chapter, provide a detailed youtube search query that can be used to find an informative educationalvideo for each chapter. Each query should give an educational informative course in youtube.` ), { title: "title of the unit", chapters:"an array of chapters, each chapter should have a youtube_search_query and a chapter_title key in the JSON object", } ); console.log(output_units); return NextResponse.json(output_units); } catch (error) { if (error instanceof ZodError) { return new NextResponse("Invalid Body", { status: 400 }); } }};
Schema file for prisma
model Course { id String @id @default(cuid()) name String image String units Unit[]}model Unit { id String @id @default(cuid()) name String chapters Chapter[] courseId String course Course @relation(fields: [courseId], references: [id]) @@index([courseId], name:"courseId")}model Chapter { id String @id @default(cuid()) unitId String name String youtubeSearchQuery String videoId String? summary String? @db.VarChar(3000) unit Unit @relation(fields: [unitId], references: [id]) questions Question[] @@index([unitId], name:"unitId")}model Question { id String @id @default(cuid()) chapterId String questionId String chapter Chapter @relation(fields: [chapterId], references: [id]) question String @db.VarChar(3000) options String @db.VarChar(3000) answer String @db.VarChar(3000) @@index([chapterId], name:"chapterId")}
My folder structure is as follows:
My Insomnia response looks like this:
I tried to hit the API with Insomnia REST but I got no response.I was expecting the following to happen: