Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ReferenceError: fetch is not defined in import OpenAI from 'openai' #304

Closed
1 task done
JackLazenbyZigzag opened this issue Sep 10, 2023 · 30 comments
Closed
1 task done
Labels
bug Something isn't working

Comments

@JackLazenbyZigzag
Copy link

JackLazenbyZigzag commented Sep 10, 2023

Hello there! There seems to have been a few issues around this that have been resolved recently, but I'm still getting it so thought I would share just in case it's something different.

Confirm this is a Node library issue and not an underlying OpenAI API issue

  • This is an issue with the Node library

Describe the bug

When building and running the OpenAI library in our NestJS API, we can do so locally with no issue ; we can communicate with it and get responses etc. However, we have a Jest testing suite, and when trying to test, it fails with:

ReferenceError: fetch is not defined

  1 | import {Injectable} from "@nestjs/common";
  2 | import {ChatCompletionMessageParam} from "openai/resources/chat";
> 3 | import OpenAI from 'openai';

at Object. (../../../../node_modules/openai/_shims/fetch.js:8:17)
at Object. (../../../../node_modules/openai/core.js:64:17)
at Object. (../../../../node_modules/openai/src/index.ts:116:7)

To Reproduce

  1. Create a NestJS API
  2. Install the openai library via npm
  3. Create a controller that returns a response when a message is passed through via POST
  4. Create a test suite in Jest to test this endpoint
  5. Error occurs

Code snippets

No response

OS

macOS

Node version

Node v18.17.1

Library version

openai 4.6.0

@JackLazenbyZigzag JackLazenbyZigzag added the bug Something isn't working label Sep 10, 2023
@rattrayalex
Copy link
Collaborator

Thanks for reporting @JackLazenbyZigzag . Can you share your jest config, tsconfig, and package.json?

@JackLazenbyZigzag
Copy link
Author

JackLazenbyZigzag commented Sep 10, 2023

No problem at all @rattrayalex! We use Nx to manage our projects via a monorepo setup so I will share the jest and ts configs for the particular project that is failing.

Here they are:

package.json:

{
    "dependencies": {
        "@angular/animations": "^15.2.0",
        "@angular/common": "^15.2.0",
        "@angular/compiler": "^15.0.0",
        "@angular/core": "^15.2.0",
        "@angular/fire": "^7.6.0",
        "@angular/forms": "^15.2.0",
        "@angular/platform-browser": "^15.2.0",
        "@angular/platform-browser-dynamic": "^15.2.0",
        "@angular/router": "^15.2.0",
        "@awesome-cordova-plugins/adjust": "^6.4.0",
        "@awesome-cordova-plugins/core": "^6.4.0",
        "@awesome-cordova-plugins/mixpanel": "^6.4.0",
        "@awesome-cordova-plugins/purchases": "^6.4.0",
        "@awesome-cordova-plugins/social-sharing": "^6.4.0",
        "@awesome-cordova-plugins/status-bar": "^6.4.0",
        "@azure/identity": "^3.3.0",
        "@azure/keyvault-secrets": "^4.7.0",
        "@azure/service-bus": "^7.9.0",
        "@capacitor/app": "^5.0.6",
        "@capacitor/camera": "^5.0.6",
        "@capacitor/core": "^5.0.6",
        "@capacitor/device": "^5.0.6",
        "@capacitor/haptics": "^5.0.6",
        "@capacitor/network": "^5.0.6",
        "@capacitor/preferences": "^5.0.6",
        "@contentful/rich-text-html-renderer": "^16.1.1",
        "@ionic/angular": "^6.7.0",
        "@microsoft/applicationinsights-web": "^3.0.2",
        "@nestjs/axios": "^0.0.8",
        "@nestjs/common": "^9.4.0",
        "@nestjs/config": "^2.3.1",
        "@nestjs/core": "^9.2.0",
        "@nestjs/cqrs": "^9.0.3",
        "@nestjs/event-emitter": "^1.4.1",
        "@nestjs/jwt": "^10.0.3",
        "@nestjs/passport": "^9.0.3",
        "@nestjs/platform-express": "^9.4.0",
        "@nestjs/schedule": "^2.2.1",
        "@nestjs/typeorm": "^9.0.1",
        "@nestjs/websockets": "^9.4.0",
        "@ngneat/until-destroy": "^10.0.0",
        "@ngrx/effects": "13.2.0",
        "@ngrx/eslint-plugin": "^16.2.0",
        "@ngrx/router-store": "15.4.0",
        "@ngrx/store": "13.2.0",
        "@ngx-translate/core": "^14.0.0",
        "app-root-path": "^3.1.0",
        "applicationinsights": "^2.7.3",
        "argon2": "^0.29.1",
        "azure-devops-node-api": "^12.1.0",
        "cache-manager": "^5.2.3",
        "canvas-confetti": "^1.6.0",
        "capacitor-native-settings": "^5.0.1",
        "capacitor-rate-app": "^4.0.3",
        "class-transformer": "^0.5.1",
        "class-validator": "^0.14.0",
        "com.adjust.sdk": "^4.32.0",
        "compass-mixins": "^0.12.12",
        "configcat-js": "^8.1.1",
        "contentful": "^9.3.5",
        "csv-parse": "5.5.0",
        "currency-symbol-map": "^5.1.0",
        "es6-promise-plugin": "^4.2.2",
        "express": "^4.17.3",
        "firebase": "^9.23.0",
        "firebase-admin": "11.10.1",
        "json-rules-engine": "^6.4.2",
        "jwks-rsa": "^2.1.3",
        "minisearch": "^6.1.0",
        "mixpanel-browser": "^2.47.0",
        "mssql": "^10.0.0",
        "ng-circle-progress": "^1.7.1",
        "ngrx-forms": "^8.0.0",
        "ngx-sse-client": "^3.0.0",
        "node-ipinfo": "^3.4.2",
        "onesignal-cordova-plugin": "^3.3.1",
        "onesignal-node": "^3.4.0",
        "openai": "^4.6.0",
        "passport": "^0.6.0",
        "passport-azure-ad": "^4.3.5",
        "passport-headerapikey": "^1.2.2",
        "passport-jwt": "^4.0.1",
        "qs": "^6.11.2",
        "reflect-metadata": "^0.1.13",
        "rxjs": "^7.8.1",
        "seedrandom": "^3.0.5",
        "shallow-equal-object": "^1.1.1",
        "sqlite3": "^5.1.5",
        "stream-chat": "^8.11.0",
        "stream-chat-angular": "^4.35.0",
        "svg-path-properties": "^1.2.0",
        "swiper": "^8.4.7",
        "tedious": "^16.4.0",
        "ts-loader": "^9.4.4",
        "tslib": "^2.6.2",
        "typeorm": "^0.3.17",
        "webpack": "5.88.2",
        "yargs": "^17.3.0",
        "zone.js": "0.13.1"
    },
    "devDependencies": {
        "@angular-devkit/build-angular": "^15.2.0",
        "@angular-eslint/eslint-plugin": "^15.2.0",
        "@angular-eslint/eslint-plugin-template": "^15.2.0",
        "@angular-eslint/template-parser": "^15.2.0",
        "@angular/cli": "^15.2.0",
        "@angular/compiler": "^15.2.0",
        "@angular/compiler-cli": "^15.2.0",
        "@angular/language-service": "^15.2.0",
        "@azure/storage-blob": "^12.15.0",
        "@capacitor/android": "^5.0.0",
        "@capacitor/cli": "^5.0.0",
        "@capacitor/ios": "^5.0.0",
        "@commitlint/cli": "^17.7.1",
        "@commitlint/config-conventional": "^17.7.0",
        "@ionic/angular-toolkit": "^10.0.0",
        "@nestjs/schematics": "^9.0.3",
        "@nestjs/testing": "^9.2.0",
        "@ngneat/spectator": "^14.0.0",
        "@ngrx/schematics": "15.4.0",
        "@ngrx/store-devtools": "15.4.0",
        "@nrwl/cypress": "14.0.5",
        "@nrwl/eslint-plugin-nx": "14.0.5",
        "@nrwl/jest": "14.0.5",
        "@nrwl/linter": "14.0.5",
        "@nrwl/nest": "14.0.5",
        "@nrwl/node": "14.0.5",
        "@nrwl/nx-cloud": "14.0.3",
        "@nrwl/workspace": "^14.0.5",
        "@nxtend/capacitor": "13.0.0",
        "@nxtend/ionic-angular": "13.1.0",
        "@swc/cli": "^0.1.55",
        "@swc/core": "^1.3.83",
        "@swc/jest": "^0.2.29",
        "@types/app-root-path": "^1.2.5",
        "@types/bcryptjs": "^2.4.3",
        "@types/canvas-confetti": "^1.6.1",
        "@types/express": "^4.17.13",
        "@types/jest": "27.4.1",
        "@types/jest-when": "^3.5.2",
        "@types/mixpanel-browser": "^2.47.1",
        "@types/mssql": "^8.1.2",
        "@types/node": "^20.6.0",
        "@types/passport-azure-ad": "^4.0.8",
        "@types/passport-http": "^0.3.8",
        "@types/passport-jwt": "^3.0.9",
        "@types/supertest": "^2.0.12",
        "@types/uuid": "^9.0.3",
        "@types/yargs": "^17.0.7",
        "@typescript-eslint/eslint-plugin": "5.60.1",
        "@typescript-eslint/parser": "5.62.0",
        "axios": "^0.27.2",
        "babel-jest": "29.6.4",
        "contentful-cli": "^2.8.6",
        "contentful-management": "^10.45.0",
        "cypress": "^12.15.0",
        "cypress-localstorage-commands": "^2.2.4",
        "dotenv": "^16.3.1",
        "eslint": "8.48.0",
        "eslint-config-prettier": "9.0.0",
        "eslint-plugin-cypress": "^2.10.3",
        "eslint-plugin-jest": "^26.0.0",
        "husky": "^8.0.3",
        "jasmine-marbles": "0.9.2",
        "jest": "27.5.1",
        "jest-extended": "^2.0.0",
        "jest-mock-extended": "^3.0.4",
        "jest-preset-angular": "13.1.1",
        "jest-when": "^3.6.0",
        "lint-staged": "^14.0.1",
        "ng-mocks": "^14.11.0",
        "node-loader": "^2.0.0",
        "nodemon": "^3.0.1",
        "nx": "^14.1.7",
        "postcss": "^8.4.29",
        "prettier": "2.8.8",
        "supertest": "^6.3.3",
        "ts-jest": "27.1.4",
        "ts-node": "^10.7.0",
        "typescript": "4.9.5"
    }
}

jest.config.js:

module.exports = {
    displayName: 'app-backend-user-e2e',
    preset: '../../../../jest.preset.js',
    globals: {
        'ts-jest': { tsconfig: '<rootDir>/tsconfig.spec.json' },
    },
    transform: {
        '^.+\\.[tj]sx?$': 'ts-jest',
    },
    moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
    coverageDirectory: '../../../../coverage/libs/app/backend/user-e2e',
};

tsconfig:

{
    "extends": "../../../../tsconfig.base.json",
    "files": [],
    "include": [],
    "references": [
        {
            "path": "./tsconfig.lib.json"
        },
        {
            "path": "./tsconfig.spec.json"
        }
    ]
}

Thanks!

@rattrayalex
Copy link
Collaborator

Thanks! It looks like your jest and tsconfig extend base configs, would you mind sharing those as well?

@rattrayalex
Copy link
Collaborator

(I believe this may be the same bug mentioned here: #243 (comment))

@JackLazenbyZigzag
Copy link
Author

@rattrayalex of course! As an FYI, I have obfsucated some things that are business specific to our project, in case it looks a bit empty! :-) We also only extend from a base tsconfig, the jest side of things just uses the rules out of the box that jest suggests.

base tsconfig:

{
    "compileOnSave": false,
    "compilerOptions": {
        "baseUrl": ".",
        "outDir": "./dist/out-tsc",
        "sourceMap": true,
        "declaration": false,
        "module": "commonjs",
        "moduleResolution": "node",
        "emitDecoratorMetadata": true,
        "experimentalDecorators": true,
        "allowSyntheticDefaultImports": true,
        "importHelpers": true,
        "target": "es2015",
        "typeRoots": ["node_modules/@types"],
        "lib": ["es2019", "dom", "es2020.string"],
        "paths": {
            "@angular/*": ["./node_modules/@angular/*"],
        },
        "rootDir": ".",
        "skipLibCheck": true,
        "noUnusedLocals": true,
        "noUnusedParameters": true,
        "noImplicitReturns": true,
        "noImplicitAny": true
    },
    "angularCompilerOptions": {
        "fullTemplateTypeCheck": true,
        "strictInjectionParameters": true
    }
}

@rattrayalex
Copy link
Collaborator

great, thanks @JackLazenbyZigzag ! We'll try to take a look at this on Monday.

@JackLazenbyZigzag
Copy link
Author

(I believe this may be the same bug mentioned here: #243 (comment))

@rattrayalex I had a look at that prior to creating this and it does look like it could be similar!

Thank you for your help, appreciate it. Have a great rest of your weekend!

@jedwards1211
Copy link

@JackLazenbyZigzag what jest testEnvironment/@jest-environment are you running your tests in, is it jsdom or something else besides 'node'?

We weren't able to reproduce the issue when using the node environment, but we can reproduce using the jsdom environment because jsdom still doesn't polyfill fetch: jsdom/jsdom#1724.

We were able to get chat completion requests working with the jsdom environment by adding import 'cross-fetch/polyfill' before any import from 'openai'. But warning: we couldn't get any file upload methods to work that way. Let me know if this works for you in the short term, we're still investigating if there are better solutions to this.

@jedwards1211
Copy link

Okay we were able to get even file uploads to work in the jsdom environment by using

/**
 * @jest-environment jsdom
 */
import 'formdata-polyfill'
import 'whatwg-fetch'
import OpenAI, { toFile } from 'openai';

in that specific order.

@JackLazenbyZigzag
Copy link
Author

JackLazenbyZigzag commented Sep 11, 2023

Hi @jedwards1211 - thanks for this! I've added the import for cross-fetch/polyfill, and that resolved it for our existing project test suite (so not adding anything new test-wise). However, when trying to test our service specifically that uses the library, it still doesn't like it. I'm getting the below from the import for formdata-polyfill:

TS7016: Could not find a declaration file for module  formdata-polyfill .
/Users/jacklazenby/WebstormProjects/zigzag/node_modules/formdata-polyfill/formdata.min.js
implicitly has an  any  type.

and, if I ignore this and try to test, I get this:

Test suite failed to run

    ReferenceError: FormData is not defined

      at Object.<anonymous> (../../../../node_modules/openai/_shims/form-data.js:5:20)
      at Object.<anonymous> (../../../../node_modules/openai/src/uploads.ts:66:15)`

This only happens when I test a file that is directly using the openai library though. If I don't do that, the test suite now runs correctly.

@jedwards1211
Copy link

@JackLazenbyZigzag would you be able to use whatwg-fetch? We weren't able to get file uploads to work with cross-fetch. Also it seems I was confused and we didn't actually need formdata-polyfill to do file uploads with whatwg-fetch.

@JackLazenbyZigzag
Copy link
Author

@jedwards1211 at this point I'm not trying to do any file related things. I've just built a very basic test suite for a basic service.ts file that handles the openai library interaction, like so:

service ts file:

import 'cross-fetch/polyfill';
import 'whatwg-fetch';
import { Injectable } from '@nestjs/common';
import { ChatCompletionMessageParam } from 'openai/resources/chat';
import OpenAI from 'openai';
import { setupMessage } from '../../../domain/openai/openai';

const openai = new OpenAI({
    apiKey: 'someapikey',
});

@Injectable()
export class OpenAIService {
    async getResponse(message: string, userId: string) {
        const messageToSendChat: ChatCompletionMessageParam = {
            content: message,
            role: 'user',
        };

        const response = await openai.chat.completions.create({
            model: 'gpt-3.5-turbo',
            messages: [setupMessage, messageToSendChat],
            user: userId,
        });

        return response.choices[0].message.content;
    }
}

test ts file:


import {OpenAIService} from './openai.service';
import {Test, TestingModule} from '@nestjs/testing';


describe('OpenAIService', () => {
    let service: OpenAIService;


    beforeEach(async () => {
        const module: TestingModule = await Test.createTestingModule({
            providers: [
                OpenAIService,
            ],
        }).compile();

        service = module.get<OpenAIService>(OpenAIService);
    });

    it('should be defined', () => {
        expect(service).toBeDefined();
    });
});

And this gives me the error:

Test suite failed to run

    ReferenceError: FormData is not defined

      at Object.<anonymous> (../../../../node_modules/openai/_shims/form-data.js:5:20)
      at Object.<anonymous> (../../../../node_modules/openai/src/uploads.ts:66:15)

@jedwards1211
Copy link

jedwards1211 commented Sep 11, 2023

Huh, in whatever version of the jest jsdom environment I have, FormData is defined...I think we could rework our shims to where you only get an error like that if you try to do a file upload.

Btw you only need one of cross-fetch/polyfill and whatwg-fetch, they're both polyfills for the fetch API.

@JackLazenbyZigzag
Copy link
Author

@jedwards1211 I have removed whatwg-fetch for now, and removed the test file. I can re-add it once a fix or workaround has been identified :-)

@rattrayalex
Copy link
Collaborator

@JackLazenbyZigzag did you install 'formdata-polyfill' before importing it?

Have you tried using the node jest environment instead of jsdom for your backend tests?

@JackLazenbyZigzag
Copy link
Author

JackLazenbyZigzag commented Sep 11, 2023

@rattrayalex yes, I have installed 'formdata-polyfill before importing it.

Also, just re-checked our jest.config for this test suite and it was always using node. Apologies for the mix up!

If I change it to jsdom, then it does pass. Obviously this isn't ideal as it could increase test run speed, and we're running this in a backend environment which shouldn't require jsdom for our needs.

@jedwards1211
Copy link

If you can create a repository that reproduces the issue it would be the most helpful, otherwise, to make sure we're on the same page, you're able to reproduce the fetch is not defined error under all of the following conditions?

  1. jest.config has testEnvironment: 'node'
  2. the test file has
    /**
     * @jest-environment node
     */
    
    (or doesn't have a @jest-environment pragma)
  3. you're running in Node >= 16

Can you also share your jest.preset.js and any related files?
I wonder if some jest plugin or config is affecting its module resolution.

@JackLazenbyZigzag
Copy link
Author

@jedwards1211 I can get a test repo put together in the next day or so if needed, but in the meantime, in response to your questions regarding environment. In our case:

  1. jest.config has testEnvironment: 'node' for all of our configs. (we have since changed one to jsdom to allow these tests to pass for openai, but when it fails it is when it is set to node)
  2. the test file does not have any other specification of environment - it picks it up from the config
  3. We are running v18.17.1 of node.

Jest.preset is pretty small, but here it is:

const nxPreset = require('@nrwl/jest/preset');

module.exports = {
    ...nxPreset,
    testPathIgnorePatterns: ['<rootDir>/src/environments/environment.test.ts'],
};

jest.config.js for this library:

module.exports = {
    displayName: 'app-backend-user',
    preset: '../../../../jest.preset.js',
    globals: {
        'ts-jest': { tsconfig: '<rootDir>/tsconfig.spec.json' },
    },
    testEnvironment: 'jsdom',
    transform: {
        '^.+\\.[tj]sx?$': 'ts-jest',
    },
    moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
    coverageDirectory: '../../../../coverage/libs/app/backend/user',
};

@jedwards1211
Copy link

jedwards1211 commented Sep 12, 2023

Okay I was able to repro on Jest 27, I was testing on Jest 29 and didn't notice that was different from your project until now. I think support for package.json exports must have been added to Jest in a later version. The solution we're working on is to have you import 'openai/shims/node' in cases like this.

@rattrayalex
Copy link
Collaborator

@dilizarov @JackLazenbyZigzag are you able to upgrade to Jest 29? If so, can you confirm the issue does not appear on that version?

@JackLazenbyZigzag
Copy link
Author

@rattrayalex we have a lot of jest dependencies in our project that mean we can't upgrade to the latest version of Jest from 27 without a lot of extra work, which I can't quickly do to investigate this I'm afraid. I will see what I can do over the next few days, though.

@rattrayalex
Copy link
Collaborator

Ah ok, don't worry about it then. We hope to have a workaround out within a day or two.

@ciryon
Copy link

ciryon commented Sep 13, 2023

We got the same problem here in AWS Lambda NodeJS 16 runtime.

@rattrayalex
Copy link
Collaborator

@ciryon can you share a minimal repo that reproduces the issue you're seeing?

(Note that Node 16 is now EOL and we'll be dropping support for it soon)

@ciryon
Copy link

ciryon commented Sep 14, 2023

@rattrayalex not easily made into a minimal repo I'm afraid. The issue appeared when upgrading from version 3 of the library, and I've tried to rollback from latest all the way to 4.0.1 and the same problem still appears.

@rattrayalex
Copy link
Collaborator

Gotcha. And you only experience the problem on Lambda, not locally?

@ciryon
Copy link

ciryon commented Sep 14, 2023

Yes, it works locally!

@ciryon
Copy link

ciryon commented Sep 14, 2023

I can also report that it works if I use the Node 18 runtime on AWS Lambda.

@rattrayalex
Copy link
Collaborator

Ah, great. Thanks Christian! Glad you've found something that works – we'll be dropping support Node 16 support in the next release.

@yardenGerecht
Copy link

For me updating all jest libraries to the latest versions helped.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

5 participants