You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Next.js 13.4.19调用OpenAI API触发headers未定义TypeError

Next.js 13.4.19 集成OpenAI API时的"Cannot read properties of undefined (reading 'headers')"错误排查

问题场景

使用Next.js 13.4.19集成OpenAI API,API路由结构配置正常,但向http://localhost:3000/api/course/createChapters发送POST请求时触发错误。请求参数:

{
    "title":"calculus",
    "units":["algebra","differentiation","integration"]
}

错误信息

- 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)

相关代码文件

gpt.ts

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 [];
}

route.ts

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 });
    }
    console.error("API Error:", error);
    return new NextResponse("Internal Server Error", { status: 500 });
  }
};

Prisma Schema

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")
}

文件夹结构

文件夹结构

预期结果

使用Insomnia发送请求后,期望得到包含章节标题和YouTube搜索词的JSON结果:
预期结果

错误原因及修复方案

核心问题

  1. 错误处理不完整:原route.ts仅捕获ZodError,其他错误未处理,导致函数返回undefined,Next.js读取响应headers时触发报错。
  2. 类型不匹配:strict_output接收数组类型的user_prompt,返回数组,但原代码中outptUnits定义为单个对象类型,导致类型冲突。
  3. 请求参数格式错误:原请求使用单引号,不符合JSON标准。

修复步骤

  1. 完善错误处理:在route.ts的catch块中添加默认错误返回,确保始终返回NextResponse。
  2. 修正类型定义:将outptUnits改为数组类型,匹配strict_output的返回值。
  3. 规范请求参数:将请求JSON中的单引号改为双引号。
  4. 验证OpenAI配置:确保OPENAI_API_KEY环境变量已正确配置。

内容的提问来源于stack exchange,提问作者pradip subedi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 00:23:11