Getting error when invoking OpenAI function in Forge

Installed openai package and getting error when using function.

Error: Error thrown in the snapshot context.
App code snapshot error: Snapshot error occurred: Error: this environment is missing the following Web Fetch API type: fetch is not defined. You may need to use polyfills
Learn more about the snapshot context at https://go.atlassian.com/forge-snapshot-context.
import 'openai/shims/web';
import api, { route, fetch } from "@forge/api";
import 'formdata-node';
import OpenAI from 'openai';
import tty from 'tty';

Here is my code snippet:

export const event = async (event, context) => {
    const issueId = event.issue.id;
    console.log('Full response of event', JSON.stringify(event, null, 2));
    try {
        // Fetch issue details from Jira
        const response = await api.asApp().requestJira(route`/rest/api/3/issue/${issueId}`);
        const issueData = await response.json();
        console.log('Full response of API request:', JSON.stringify(response, null, 2));

        const summary = issueData.fields.summary;
        console.log('summary:', summary);
        const text = issueData.fields.description.content[0].content[0].text;
        console.log('description:', text);

// Function to get embeddings from OpenAI
        const getEmbedding = async (text) => {
            tty.isatty = () => {
                return false
            };
            const configuration = {
                apiKey: process.env.OPEN_API_KEY,
                organisation: process.env.OPEN_ORG_ID
            };
            console.log('OpenAIKey:', process.env.OPEN_API_KEY);
            console.log('OpenOrg:', process.env.OPEN_ORG_ID);
            const openai = new OpenAI(configuration);
            console.log('Text for OpenAI API:', text);
            console.log('Full response of OpenAI GetEmbedding API request:', JSON.stringify(getEmbedding(text), null, 2));
            const embedding = await openai.embeddings.create({
                model: 'text-embedding-3-large',
                input: text,
                encoding_format: 'float'
            });

I have tried using fetch method and it worked, but couldn’t make it work while importing package, even though I have seen an article where it uses OpenAI package and its functions.

Thank you in advance for any hint or ideas.

@Robert_Mian Have you tried the new native Node.js runtime? It should be more compatible with packages like openai and you probably won’t need to import the OpenAI web shims.

1 Like

Hi, unfortunately, the native Node runtime doesn’t support the new native fetch built into NodeJS, which this OpenAI library might be using. Currently, we have only polyfilled https. It is a known issue and is on our backlog to also polyfill the native fetch.

I know it is annoying, are you able to get by with calling OpenAI’s REST APIs directly in the meantime?

@BoZhang I updated runtime as @klaussner advised, and could overcome the error and deploy. However, openai function still does not invoke. Supposedly, it is because of the limitation you mentioned. Here is the script for more clarity:

export const event = async (event, context) => {
    const issueId = event.issue.id;
    console.log('Full response of event', JSON.stringify(event, null, 2));
    try {
        // Fetch issue details from Jira
        const response = await api.asApp().requestJira(route`/rest/api/3/issue/${issueId}`);
        const issueData = await response.json();
        console.log('Full response of API request response:', JSON.stringify(response, null, 2));

        const summary = issueData.fields.summary;
        console.log('summary:', summary);
        const text = issueData.fields.description.content[0].content[0].text;
        console.log('description:', text);

        const getEmbedding = async (text) => {
            tty.isatty = () => { return false };
            const configuration = {
                apiKey: process.env.OPEN_API_KEY,
                organisation: process.env.OPEN_ORG_ID
            };
            console.log('OpenAIKey:', process.env.OPEN_API_KEY);
            console.log('OpenOrg:', process.env.OPEN_ORG_ID);
            const openai = new OpenAI(configuration);
            console.log('Text for OpenAI API:', text);
            console.log('Full response of OpenAI GetEmbedding API request:', JSON.stringify(getEmbedding(text), null,2));
            const embeddingResponse = await openai.embeddings.create({
                model: 'text-embedding-3-large',
                input: text,
                encoding_format: 'float'
            });
            return embeddingResponse.data[0].embedding;
        };
        const embedding = await getEmbedding(text);
        console.log('Embedding:', JSON.stringify(embedding, null, 2));

        return {
            body: 'Event handled successfully',
        };
    } catch (error) {
        console.log('Error fetching issue data or OpenAI response:', error);
        return {
            body: 'Error handling event',
        };
    }
};

I think openai is using the node-fetch package and not the native fetch function. I tried it out with this web trigger example, which returns the embedding as expected:

import OpenAI from "openai";

const openai = new OpenAI();

export async function run() {
  const result = await openai.embeddings.create({
    model: "text-embedding-3-small",
    input: "Example"
  });

  return {
    statusCode: 200,
    headers: { "Content-Type": ["application/json"] },
    body: JSON.stringify(result)
  };
}
1 Like

Ah ok, thanks for the heads up; if it’s using the node-fetch package, then it should work (since it is using https under the hood).

@Robert_Mian, are you able to share more details on the error message that you are seeing?
Could it be that you need to include the domain that the SDK is egressing to in the external permissions of the manifest?

1 Like

@BoZhang I have added permission to make requests to openai.com, I hope I did it correctly. Please have a look:

app:
  id: ari:cloud:ecosystem::app/xxxxxxxxxxxxxxxxxx
  runtime:
    name: nodejs18.x
permissions:
  scopes:
    - read:jira-work
  external:
    fetch:
      backend:
        - "*.openai.com"

And I wonder maybe I should add in manifest file something about “getEmbedding” or “embedding” from my code. I’m thinking maybe only exporting “event” function is not enough?:

export const event = async (event, context) => {
    const issueId = event.issue.id;
    try {
        // Fetch issue details from Jira
        const response = await api.asApp().requestJira(route`/rest/api/3/issue/${issueId}`);
        const issueData = await response.json();

        const summary = issueData.fields.summary;
        const text = issueData.fields.description.content[0].content[0].text;
        const getEmbedding = async (text) => {
            const configuration = {
                apiKey: process.env.OPEN_API_KEY,
                organisation: process.env.OPEN_ORG_ID
            };
            const openai = new OpenAI(configuration);
            const embeddingResponse = await openai.embeddings.create({
                model: 'text-embedding-3-large',
                input: text,
                encoding_format: 'float'
            });
            return embeddingResponse.data[0].embedding;
        };
        const embedding = await getEmbedding(text);
        return {
            body: 'Event handled successfully',
        };
    } catch (error) {
        console.log('Error fetching issue data or OpenAI response:', error);
        return {
            body: 'Error handling event',
        };
    }
};

@BoZhang and @klaussner thank you for your help. Figured it out. I have used resolver function and added it to manifest, and moved “getEmbedding” function out of “event” function and used a resolver to invoke “getEmbedding” function and it worked finally.

export const event = async (event, context) => {
    const issueId = event.issue.id;
    console.log('Full response of event', JSON.stringify(event, null, 2));
    try {
        // Fetch issue details from Jira
        const response = await api.asApp().requestJira(route`/rest/api/3/issue/${issueId}`);
        const issueData = await response.json();
        console.log('Full response of API request response:', JSON.stringify(response, null, 2));

        const summary = issueData.fields.summary;
        console.log('summary:', summary);
        const text = issueData.fields.description.content[0].content[0].text;
        console.log('description:', text);
        const embedding = await getEmbedding({ text });
        console.log('Text:', text);
        console.log('Embedding:', JSON.stringify(embedding, null, 2));
        return {
            body: 'Event handled successfully',
        };
    } catch (error) {
        console.log('Error fetching issue data or OpenAI response:', error);
        return {
            body: 'Error handling event',
        };
    }
};
export const getEmbedding = async ({ text }) => {
    const configuration = {
        apiKey: process.env.OPEN_API_KEY,
        organisation: process.env.OPEN_ORG_ID
    };
    const openai = new OpenAI(configuration);
    const embeddingResponse = await openai.embeddings.create({
        model: 'text-embedding-3-large',
        input: text,
        encoding_format: 'float'
    });
    return embeddingResponse.data[0].embedding;
};
const resolver = new Resolver();
resolver.define('getEmbedding', getEmbedding);

export const handler = resolver.getDefinitions();

Manifest Edit:

  function:
    - key: run
      handler: index.run
    - key: event
      handler: index.event
    - key: getEmbedding
      handler: index.getEmbedding
    - key: getEmbeddingResolver
      handler: index.handler

Finally, do I understand it correctly that I should have used resolver in the first place, because “event” that was used in the content of “Trigger” module is being executed on server-side, and not using resolver for my “getEmbedding” function caused a failure of its execution, because no client-side UI has been triggered?