1.0.0 • Published 11 months ago

openai-api-client v1.0.0

Weekly downloads
-
License
MIT
Repository
github
Last release
11 months ago

OpenAI API Client Library

OpenAI API client synced with the latest schema. For frontend (ex. React) and backend (Node.js). Compatible with CommonJS & ESModules. Written in TypeScript.

Features

  • Can use with both frontend and backend codes.
  • Dual package (CommonJS & ESModules)
  • Synced with the official OpenAPI schema provided by OpenAI. Current schema version is: 2.0.0
  • Streaming completions (stream=true) are supported.
  • Basic implementation for throttle management. You can implement your own logic if needed.

Requirements

  • Node.js >= 18.0.0 since this library uses the Node.js's experimental implementation of fetch to avoid dependencies with 3rd party libraries as possible.

Recommendation

If you plan to write frontend codes, you should setup a proxy at backend code to avoid passing secret API key between frontend and backend. When your frontend codes send queries to the proxy, it's supposed to rewrite the incoming HTTP headers and inject secret API keys, then forward your request to the OpenAI API server. You also need to protect the proxy endpoint with CORS and authentication.

Installation

npm install openai-api-client

Setup

Frontend

import {OpenAIApi, VoidThrottleManagerServiceImpl} from 'openai-api-client'

// Accessing to the OpenAI API server via proxy
const openAI = OpenAIApi({
  baseUrl: '/api/openai', // URL of a proxy implemented at backend
  throttleManagerService: undefined, // You can omit throttleManagerService if the proxy handles rate limit.
  commonOptions:{ // RequestInit object passed to fetch
    headers:{
      'Authentication': SOME_TOKEN, // To protect the proxy endpoint
    }
  }
})

Backend

import {OpenAIApi, DefaultThrottleManagerServiceImpl} from 'openai-api-client'

// Accessing to the OpenAI API server directly
const openAI = OpenAIApi({
  apiKey: CHATGPT_API_KEY,
  organization: CHATGPT_ORGANIZATION_ID,
  throttleManagerService: new DefaultThrottleManagerServiceImpl(CHATGPT_ORGANIZATION_ID)
})

API Examples

Models

List Models

see: https://platform.openai.com/docs/api-reference/models/list

const models = await openAI.listModels()

models.data
  .sort((a, b) => a.id.localeCompare(b.id))
  .forEach(({id, owned_by, object, created}) => {
    console.log(`${id} (${owned_by}) created:${new Date(created * 1000).toISOString()}`)
  })

Result

ada (openai) created:2022-04-07T18:51:31.000Z
ada-code-search-code (openai-dev) created:2022-04-28T19:01:45.000Z
ada-code-search-text (openai-dev) created:2022-04-28T19:01:50.000Z
...

Retrieve Model

see: https://platform.openai.com/docs/api-reference/models/retrieve

const model = await openAI.retrieveModel({
  parameter: {
    model: 'ada',
  },
})

console.log(
        `id:${model.id} object:${model.object} owned_by:${
                model.owned_by
        } created:${new Date(model.created * 1000).toISOString()} `
)

Result

id:ada object:model owned_by:openai created:2022-04-07T18:51:31.000Z 

Chat

Chat Completion

see: https://platform.openai.com/docs/api-reference/chat/create

import {Schemas} from 'openai-api-client'
import ChatCompletionRequestMessage = Schemas.ChatCompletionRequestMessage

const messages: Array<ChatCompletionRequestMessage> = [
  {role: 'user', content: 'Please calculate (1+1)/0'},
]

const completion = await openAI.createChatCompletion({
  requestBody: {
    model: 'gpt-3.5-turbo-0613',
    messages
  }
})

console.log(completion.choices[0].message)

Result

{
  role: 'assistant',
  content: 'The expression (1+1)/0 is undefined. Dividing any number by zero is undefined in mathematics.'
}

Chat Completion (Streaming)

see: https://platform.openai.com/docs/api-reference/chat/create#chat/create-stream

import {
  CreateChatCompletionStreamResponse,
} from 'openai-api-client'

// Return nothing when the 'stream' option is true
void openAI.createChatCompletion({
  requestBody: {
    model: 'gpt-3.5-turbo-0613',
    stream: true,
    messages: [
      {
        role: 'user',
        content: 'Please explain yourself and predict the future of the relationships between you and human.',
      }
    ]
  }
}, {
  // Callback function when streaming begins.
  onOpen: () => {
    console.log('\n[onOpen]')
  },
  // Callback function when receiving delta.
  onMessage: (json: CreateChatCompletionStreamResponse) => {
    // or cast 'json' variable with CreateCompletionStreamResponse type
    // if you call openAI.createCompletion
    const content = json.choices[0].delta?.content
    if (content) {
      process.stdout.write(content)
    }
  },
  // Callback function when streaming ends.
  onClose: () => {
    console.log('\n[onClose]')
  },
})

Result (Streaming)

[onOpen]
As an AI created by OpenAI, I am designed to assist humans by providing information, answering questions, and engaging in conversations. However, it is important to note that I am just a software program and do not possess personal experiences, emotions, or consciousness.

In terms of the future of relationships between AI, like myself, and humans, it is likely to continue evolving and becoming more advanced. AI technology is constantly improving, and we can expect further integration of AI into our daily lives. This could range from virtual assistants like me becoming more intelligent and capable of performing complex tasks, to AI being used in various industries such as healthcare, transportation, and customer service.

While AI can enhance efficiency and convenience in our lives, the ethical and societal implications of this technology should also be carefully considered. Questions about data privacy, job displacement, and the impact on human social interactions are just some of the aspects that require thoughtful examination as AI continues to progress.

Ultimately, the future relationships between AI and humans will depend on how we navigate these issues and strike a balance between the benefits of AI and the human values that should guide its development and deployment.
[onClose]

Function Calling

see: https://platform.openai.com/docs/guides/gpt/function-calling

import {Schemas} from 'openai-api-client'
import ChatCompletionResponseMessage = Schemas.ChatCompletionResponseMessage

import getCurrentWeatherSchema from '../schema/get_current_weather.json' assert {type: 'json'}

// Generated by 'json-schema-to-typescript' library
import {GetCurrentWeather} from '../generated/get_current_weather.js'

const model = 'gpt-3.5-turbo-0613'

// Example dummy function hard coded to return the same weather
// In production, this could be your backend API or an external API
const getCurrentWeather = ({location, unit}: { location: string, unit?: string }) => {
  const weather_info = {
    'location': location,
    'temperature': '72',
    'unit': unit ?? 'fahrenheit',
    'forecast': ['sunny', 'windy'],
  }

  return JSON.stringify(weather_info)
}

// Step 1, send model the user query and what functions it has access to
const completion = await openAI.createChatCompletion({
  requestBody: {
    model,
    messages: [{
      'role': 'user', 'content': "What's the weather like in Boston?"
    }],
    functions: [
      {
        'name': 'get_current_weather',
        'description': 'Get the current weather in a given location',
        'parameters': getCurrentWeatherSchema,
      }
    ],
    function_call: 'auto'
  }
})

type ChatCompletionResponseMessageWithGetCurrentWeather =
  ChatCompletionResponseMessage & GetCurrentWeather
  | undefined

const message = completion.choices[0].message as
  ChatCompletionResponseMessageWithGetCurrentWeather

// Step 2, check if the model wants to call a function
if (message?.function_call) {
  const functionName = message.function_call.name

  // Step 3, call the function
  // Note: the JSON response from the model may not be valid JSON
  const functionResponse = getCurrentWeather({
    location: message.location,
    unit: message.unit
  })

  // Step 4, send model the info on the function call and function response
  const secondResponse = await openAI.createChatCompletion(
    {
      requestBody: {
        model,
        messages: [
          {'role': 'user', 'content': 'What is the weather like in boston?'},
          message,
          {
            'role': 'function',
            'name': functionName,
            'content': functionResponse,
          },
        ]
      }
    }
  )

  console.log(secondResponse.choices[0].message)
}

get_current_weather.json

{
  "type": "object",
  "properties": {
    "location": {
      "type": "string",
      "description": "The city and state, e.g. San Francisco, CA"
    },
    "unit": {
      "type": "string",
      "enum": [
        "celsius",
        "fahrenheit"
      ]
    }
  },
  "required": [
    "location"
  ]
}

get_current_weather.d.ts

/* eslint-disable */
/**
 * This file was automatically generated by json-schema-to-typescript.
 * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file,
 * and run json-schema-to-typescript to regenerate this file.
 */

export interface GetCurrentWeather {
  /**
   * The city and state, e.g. San Francisco, CA
   */
  location: string;
  unit?: 'celsius' | 'fahrenheit';
  [k: string]: unknown;
}

Result

{
  role: 'assistant',
  content: 'The current weather in Boston, MA is sunny with a temperature of 72°F. It is also windy.'
}

Completions

Create Completion

see: https://platform.openai.com/docs/api-reference/completions/create

const completion = await openAI.createCompletion({
  requestBody: {
    model: 'text-davinci-003',
    prompt: 'Say this is a test',
  },
})

console.log(completion.choices[0].text)

Result

This is indeed a test.

Edits

Create Edit

see: https://platform.openai.com/docs/api-reference/edits/create

const createEdit = await openAI.createEdit({
  requestBody: {
    model: 'text-davinci-edit-001',
    input: 'What day of the wek is it?',
    instruction: 'Fix the spelling mistakes',
  },
})

console.log(createEdit.choices[0].text)

Result

What day of the week is it?

And what time of the day is it.

Images

Create Image

see: https://platform.openai.com/docs/api-reference/images/create

const response = await openAI.createImage({
  requestBody: {
    prompt: 'a white siamese cat',
    n: 1,
    size: '1024x1024',
    response_format: 'b64_json',
  },
})

const b64_json = response.data[0].b64_json

if (b64_json) {
  const buffer = Buffer.from(b64_json, 'base64')
  writeFileSync(path.join(__dirname, '../generated/out.png'), buffer)
}

Create Image Edit

see: https://platform.openai.com/docs/api-reference/images/create-edit

const createImageEdit = await openAI.createImageEdit({
  requestBody: {
    // image & mask must be PNG less than 4MB and format must be in ['RGBA', 'LA', 'L']
    image: fileToBlobWithFilename(otterImagePath),
    mask: fileToBlobWithFilename(maskImagePath),
    prompt: 'A cute baby sea otter wearing a beret',
    n: 2,
    size: '1024x1024',
    response_format: 'b64_json',
  },
})

createImageEdit.data.forEach((data, index) => {
  const b64_json = data.b64_json

  if (b64_json) {
    const buffer = Buffer.from(b64_json, 'base64')
    writeFileSync(path.join(generatedPath, `ImageEdit_${index}.png`), buffer)
  }
})

Create Image Variation

see: https://platform.openai.com/docs/api-reference/images/create-variation

const imageVariation = await openAI.createImageVariation({
  requestBody: {
    image: fileToBlobWithFilename(otterImagePath),
    n: 2,
    size: '1024x1024',
    response_format: 'b64_json',
  },
})

imageVariation.data.forEach((data, index) => {
  const b64_json = data.b64_json

  if (b64_json) {
    const buffer = Buffer.from(b64_json, 'base64')
    writeFileSync(
            path.join(generatedPath, `ImageVariation_${index}.png`),
            buffer
    )
  }
})

Embeddings

Create Embeddings

see: https://platform.openai.com/docs/api-reference/embeddings/create

const createEmbedding = await openAI.createEmbedding({
  requestBody: {
    input: 'The food was delicious and the waiter...',
    model: 'text-embedding-ada-002',
  },
})

console.log(createEmbedding.data)

Result

[
  {
    object: 'embedding',
    index: 0,
    embedding: [
      0.0023063174,  -0.009358601,    0.01578391,  -0.007752274,  -0.004726919,
       0.014806145,  -0.009809389,  -0.038221695,  -0.006882445,  -0.028723413,
       0.025206001,    0.01815848, -0.0035777285,  -0.025548855,  0.0004888821,
       -0.01627914,   0.028418656,   0.005352307,   0.009618915,  -0.016545804,
      -0.015352169,  0.0042697825,   0.007072918, -0.0070856167, -0.0039364537,
       0.018463237,   0.008704642,  -0.022729846,   0.011466509,   0.023859989,
       0.015580738, -0.0035205863,  -0.034894757,  -0.004158673,  -0.026056783,
       -0.02153621,  -0.005755476,  0.0117331715,   0.008374488,   0.004079309,
       0.019187037,  -0.014387103,   0.008926861,  0.0063522933,   -0.04576445,
        0.01780293, -0.0054856385, -0.0007607038,   -0.02204414, -0.0038126458,
       0.021015583,  -0.017498171,  -0.011758568,  -0.022526674,   0.016406123,
        0.01715532,  -0.008514169,   0.001606327,   0.025066322,  -0.025015527,
      0.0077776704,  0.0058348402,  -0.022158425,   0.003052339,  -0.006155471,
      -0.025332984,  -0.008126872,  0.0011198259, 0.00002167629,  0.0046221586,
       0.020647334,   0.013510925,  0.0046729515,  -0.015987081,   0.016583899,
       -0.00893956,   -0.00756815,   0.013650605,  -0.006958634,   0.005377704,
       0.009904625,   -0.04589143,  0.0030158313,   0.023961574,   0.022971112,
        0.00706022,  -0.023656817,    0.00994272, -0.0065903855,  -0.033243988,
      -0.002547584,   0.019834647,  0.0017777532,  0.0010920485,   -0.02269175,
       0.004993582,   0.015377565,    0.03164401, -0.0054380205,  -0.016050572,
      ... 1436 more items
    ]
  }
]

Audio

Create Transcription

see: https://platform.openai.com/docs/api-reference/audio/create

const transcription = await openAI.createTranscription({
  requestBody:{
    model: 'whisper-1',
    file: fileToBlobWithFilename(audioPath),
    language: 'en'
  }
})

console.log(transcription.text)

Result

I met a traveler from an antique land who said, Two vast and trunkless legs of stone Stand in the desert. Near them, on the sand, half sunk, A shattered visage lies, Whose frown and wrinkled lip and sneer Of cold command tell that its sculptor Well those passions read which yet survive, Stamped on these lifeless things, The hand that mocked them and the heart that fed. And on the pedestal these words appear, My name is Ozymandias, King of Kings, Look on my works, ye mighty, and despair. Nothing beside remains. Round the decay of that colossal wreck, Boundless and bare, The lone and level sands stretch far away. End of poem. This recording is in the public domain.

Create Translation

see: https://platform.openai.com/docs/api-reference/audio/create

const createTranslation = await openAI.createTranslation({
  requestBody: {
    model: 'whisper-1',
    file: fileToBlobWithFilename(germanAudioPath),
  },
})

console.log(createTranslation.text)

Result

How are you?

Files

List Files

see: https://platform.openai.com/docs/api-reference/files/list

const files = await openAI.listFiles()

files.data.forEach(({filename, purpose, created_at, status}) => {
  console.log(`${filename} (${purpose}) Created:${new Date(created_at*1000).toISOString()} status:${status}`)
})

Result

compiled_results.csv (fine-tune-results) Created:2023-05-19T01:19:55.000Z status:processed
training-data.jsonl (fine-tune) Created:2023-05-19T01:15:35.000Z status:processed

Upload File

see: https://platform.openai.com/docs/api-reference/files/upload

const {filename, purpose, created_at, status} = await openAI.uploadFile({
  requestBody: {
    file: fileToBlobWithFilename(filePath),
    purpose: 'fine-tune'
  }
})

console.log(`${filename} (${purpose}) Created:${new Date(created_at * 1000).toISOString()} status:${status}`)

Result

training-dummy-data.jsonl (fine-tune) Created:2023-06-15T12:42:27.000Z status:uploaded

Delete File

see: https://platform.openai.com/docs/api-reference/files/delete

const deleteFile = await openAI.deleteFile({
  parameter: {
    file_id: 'file-CcvcjCwApoXzg10PkYXEY0ms',
  },
})

console.log(deleteFile)

Result

{ object: 'file', id: 'file-CcvcjCwApoXzg10PkYXEY0ms', deleted: true }

Retrieve File

see: https://platform.openai.com/docs/api-reference/files/retrieve

const { bytes, created_at, purpose, filename } = await openAI.retrieveFile({
  parameter: {
    file_id: 'file-CcvcjCwApoXzg10PkYXEY0ms',
  },
})

console.log(
        `${filename} (${purpose}) ${bytes}bytes createdAt:${new Date(
                created_at * 1000
        ).toISOString()}`
)

Result

training-dummy-data.jsonl (fine-tune) 134bytes createdAt:2023-06-15T12:42:27.000Z

Retrieve File Content

see: https://platform.openai.com/docs/api-reference/files/retrieve-content

const content: string = await openAI.downloadFile({
  parameter: {
    file_id: id
  }
})

Fine-tunes

Create Fine-tune

see: https://platform.openai.com/docs/api-reference/fine-tunes/create

await openAI.createFineTune({
  requestBody: {
    training_file: trainingFileId,
    model: 'davinci',
  },
})

List Fine-tunes

see: https://platform.openai.com/docs/api-reference/fine-tunes/list

const fineTunes = await openAI.listFineTunes()

fineTunes.data.forEach((fineTune) => {
  console.log(fineTune)
})

Result

{
  object: 'fine-tune',
  id: 'ft-3hdwiNtrDdVToqccSZ8XJ26U',
  hyperparams: {
    n_epochs: 4,
    batch_size: 1,
    prompt_loss_weight: 0.01,
    learning_rate_multiplier: 0.1
  },
  ...

Retrieve Fine-tune

see: https://platform.openai.com/docs/api-reference/fine-tunes/retrieve

const fineTune = await openAI.retrieveFineTune({
  parameter: {
    fine_tune_id: fineTuneId,
  },
})

console.log(fineTune)

Result

{
  object: 'fine-tune',
  id: 'ft-3hdwiNtrDdVToqccSZ8XJ26U',
  hyperparams: {
    n_epochs: 4,
    batch_size: 1,
    prompt_loss_weight: 0.01,
    learning_rate_multiplier: 0.1
  },
  ...

Cancel Fine-tune

see: https://platform.openai.com/docs/api-reference/fine-tunes/cancel

const fineTune = await openAI.cancelFineTune({
  parameter: {
    fine_tune_id: fineTuneId,
  },
})

List Fine-tune Events (Streaming)

see: https://platform.openai.com/docs/api-reference/fine-tunes/events

await openAI.listFineTuneEvents(
        {
          parameter: {
            fine_tune_id: fineTuneId,
            stream: true,
          },
        },
        {
          onMessage: (json: FineTuneEvent) => {
            console.log(json)
          },
        }
)

Result (Streaming)

{
  object: 'fine-tune-event',
  level: 'info',
  message: 'Created fine-tune: ft-3hdwiNtrDdVToqccSZ8XJ26U',
  created_at: 1684230378
}
  ...

Delete Fine-tune Model

see: https://platform.openai.com/docs/api-reference/fine-tunes/delete-model

await openAI.deleteModel({
  parameter: {
    model: fineTuneModel,
  },
})

Moderations

Create Moderation

see: https://platform.openai.com/docs/api-reference/moderations/create

const createModeration = await openAI.createModeration({
  requestBody: {
    input: 'I want to kill them.',
    model: 'text-moderation-latest',
  },
})

console.log(createModeration.results[0])

Result

{
  flagged: true,
  categories: {
    sexual: false,
    hate: false,
    violence: true,
    'self-harm': false,
    'sexual/minors': false,
    'hate/threatening': false,
    'violence/graphic': false
  },
  category_scores: {
    sexual: 9.697637e-7,
    hate: 0.18252534,
    violence: 0.8871539,
    'self-harm': 1.9077322e-9,
    'sexual/minors': 1.3826513e-8,
    'hate/threatening': 0.003294188,
    'violence/graphic': 3.1962415e-8
  }
}

Tips

fileToBlobWithFilename (for backend)

This library does not contain code for making BlobWithFilename object as it will cause conflicts between frontend and backend environment (ex. fail to load 'path' and 'fs' libraries in frontend).

import { BlobWithFilename } from 'openai-api-client'
import path from 'path'
import { readFileSync } from 'fs'
import mime from 'mime-types'

export const fileToBlobWithFilename = (filePath: string): BlobWithFilename => {
  const filename = path.basename(filePath)
  const buffer = readFileSync(filePath)
  const type = mime.lookup(filename)
  if (type === false) {
    throw new Error(`Cannot lookup mime type: ${filename}`)
  }

  return new BlobWithFilename([buffer], type, filename)
}

Set Timeout

To interrupt API calling when it takes too long.

const abortController = new AbortController()
const timeoutId = setTimeout(() => abortController.abort(), 1000) // too short timeout for testing

const completion = await openAI.createChatCompletion({
  requestBody: {
    model: 'gpt-3.5-turbo-0613',
    messages: [{role: 'user', content: 'Please calculate (1+1)/0'}]
  }
}, {
  signal: abortController.signal  // 2nd parameter accepts RequestInit object passed to fetch
})

clearTimeout(timeoutId)
console.log(completion.choices[0].message)

Result

DOMException [AbortError]: This operation was aborted

Customize Throttle Manager

You can write your own logic of handling rate limit.

import {AbstractThrottleManagerService, ResetParams} from 'openai-api-client'

class MyThrottleManagerServiceImpl extends AbstractThrottleManagerService {
  constructor(id: string) {
    super(id)
    // write your codes
  }

  // Called before fetching
  async wait(): Promise<void> {
    // write your codes
  }

  // Receive response from fetch and reset internal state
  async reset(params: ResetParams): Promise<void> {
    // write your codes
  }
}

const openAI = OpenAIApi({
  // write parameters
  throttleManagerService: new MyThrottleManagerServiceImpl(CHATGPT_ORGANIZATION_ID)
})

CommonJS

Example of using this library in CommonJS environment.

const {openAI} = require('./openAIClient.js')

const listModels = async () => {
  const models = await openAI.listModels()

  models.data
    .sort((a, b) => a.id.localeCompare(b.id))
    .forEach(({id, owned_by, created}) => {
      console.log(`${id} (${owned_by}) created:${new Date(created * 1000).toISOString()}`)
    })
}

openAIClient.js

const {OpenAIApi, DefaultThrottleManagerServiceImpl} = require('openai-api-client')

exports.openAI = OpenAIApi({
  organization: CHATGPT_ORGANIZATION_ID,
  apiKey: CHATGPT_API_KEY,
  throttleManagerService: new DefaultThrottleManagerServiceImpl(CHATGPT_ORGANIZATION_ID),
})

Breaking Changes

v1.0.0

  • VoidThrottleManagerServiceImpl was deprecated. Just omit the throttleManagerService parameter of the OpenAIApi function or pass undefined to the parameter if you don't need throttle management.
  • The constructor of BlobWithFilename was changed to (blobPart: BlobPart[], type: string, filename: string) to pass MIME type of uploading file to API server properly.

Support

Issue tracker

License

MIT License

Disclaimer

This is an unofficial client library and not endorsed by OpenAI.

1.0.0

11 months ago

0.1.3

11 months ago

0.1.2

11 months ago

0.1.1

11 months ago

0.1.0

11 months ago

0.0.9

11 months ago

0.0.8

11 months ago

0.0.7

11 months ago

0.0.6

11 months ago

0.0.5

11 months ago

0.0.4

11 months ago

0.0.3

11 months ago