Skip to content

Commit

Permalink
Merge pull request #335 from openai/release-please--branches--master-…
Browse files Browse the repository at this point in the history
…-changes--next--components--openai
  • Loading branch information
athyuttamre authored Sep 29, 2023
2 parents 18db571 + 3e28dcf commit eddb247
Show file tree
Hide file tree
Showing 10 changed files with 140 additions and 102 deletions.
2 changes: 1 addition & 1 deletion .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
".": "4.10.0"
".": "4.11.0"
}
22 changes: 22 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,27 @@
# Changelog

## 4.11.0 (2023-09-29)

Full Changelog: [v4.10.0...v4.11.0](https://github.com/openai/openai-node/compare/v4.10.0...v4.11.0)

### Features

* **client:** handle retry-after with a date ([#340](https://github.com/openai/openai-node/issues/340)) ([b6dd384](https://github.com/openai/openai-node/commit/b6dd38488ea7cc4c22495f16d027b7ffdb87da53))
* **package:** export a root error type ([#338](https://github.com/openai/openai-node/issues/338)) ([462bcda](https://github.com/openai/openai-node/commit/462bcda7140611afa20bc25de4aec6d4b205b37d))


### Bug Fixes

* **api:** add content_filter to chat completion finish reason ([#344](https://github.com/openai/openai-node/issues/344)) ([f10c757](https://github.com/openai/openai-node/commit/f10c757d831d90407ba47b4659d9cd34b1a35b1d))


### Chores

* **internal:** bump lock file ([#334](https://github.com/openai/openai-node/issues/334)) ([fd2337b](https://github.com/openai/openai-node/commit/fd2337b018ab2f31bcea8f9feda0ddaf755390c7))
* **internal:** update lock file ([#339](https://github.com/openai/openai-node/issues/339)) ([1bf84b6](https://github.com/openai/openai-node/commit/1bf84b672c386f8ca46bb8fc120eb8d8d48b3a82))
* **internal:** update lock file ([#342](https://github.com/openai/openai-node/issues/342)) ([0001f06](https://github.com/openai/openai-node/commit/0001f062728b0e2047d2bf03b9d947a4be0c7206))
* **internal:** update lock file ([#343](https://github.com/openai/openai-node/issues/343)) ([a02ac8e](https://github.com/openai/openai-node/commit/a02ac8e7f881551527a3cbcadad53b7e424650e8))

## 4.10.0 (2023-09-21)

Full Changelog: [v4.9.1...v4.10.0](https://github.com/openai/openai-node/compare/v4.9.1...v4.10.0)
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "openai",
"version": "4.10.0",
"version": "4.11.0",
"description": "Client library for the OpenAI API",
"author": "OpenAI <support@openai.com>",
"types": "dist/index.d.ts",
Expand Down
67 changes: 39 additions & 28 deletions src/core.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import { VERSION } from './version';
import { Stream } from './streaming';
import { APIError, APIConnectionError, APIConnectionTimeoutError, APIUserAbortError } from './error';
import {
OpenAIError,
APIError,
APIConnectionError,
APIConnectionTimeoutError,
APIUserAbortError,
} from './error';
import {
kind as shimsKind,
type Readable,
Expand Down Expand Up @@ -440,7 +446,7 @@ export abstract class APIClient {
if (value === null) {
return `${encodeURIComponent(key)}=`;
}
throw new Error(
throw new OpenAIError(
`Cannot stringify type ${typeof value}; Expected string, number, boolean, or null. If you need to pass nested query parameters, you can manually encode them, e.g. { query: { 'foo[key1]': value1, 'foo[key2]': value2 } }, and please open a GitHub issue requesting better support for your use case.`,
);
})
Expand Down Expand Up @@ -503,32 +509,37 @@ export abstract class APIClient {
retriesRemaining -= 1;

// About the Retry-After header: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After
//
// TODO: we may want to handle the case where the header is using the http-date syntax: "Retry-After: <http-date>".
// See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After#syntax for details.
const retryAfter = parseInt(responseHeaders?.['retry-after'] || '');
let timeoutMillis: number | undefined;
const retryAfterHeader = responseHeaders?.['retry-after'];
if (retryAfterHeader) {
const timeoutSeconds = parseInt(retryAfterHeader);
if (!Number.isNaN(timeoutSeconds)) {
timeoutMillis = timeoutSeconds * 1000;
} else {
timeoutMillis = Date.parse(retryAfterHeader) - Date.now();
}
}

const maxRetries = options.maxRetries ?? this.maxRetries;
const timeout = this.calculateRetryTimeoutSeconds(retriesRemaining, retryAfter, maxRetries) * 1000;
await sleep(timeout);
// If the API asks us to wait a certain amount of time (and it's a reasonable amount),
// just do what it says, but otherwise calculate a default
if (
!timeoutMillis ||
!Number.isInteger(timeoutMillis) ||
timeoutMillis <= 0 ||
timeoutMillis > 60 * 1000
) {
const maxRetries = options.maxRetries ?? this.maxRetries;
timeoutMillis = this.calculateDefaultRetryTimeoutMillis(retriesRemaining, maxRetries);
}
await sleep(timeoutMillis);

return this.makeRequest(options, retriesRemaining);
}

private calculateRetryTimeoutSeconds(
retriesRemaining: number,
retryAfter: number,
maxRetries: number,
): number {
private calculateDefaultRetryTimeoutMillis(retriesRemaining: number, maxRetries: number): number {
const initialRetryDelay = 0.5;
const maxRetryDelay = 2;

// If the API asks us to wait a certain amount of time (and it's a reasonable amount),
// just do what it says.
if (Number.isInteger(retryAfter) && retryAfter <= 60) {
return retryAfter;
}

const numRetries = maxRetries - retriesRemaining;

// Apply exponential backoff, but not more than the max.
Expand All @@ -537,7 +548,7 @@ export abstract class APIClient {
// Apply some jitter, plus-or-minus half a second.
const jitter = Math.random() - 0.5;

return sleepSeconds + jitter;
return (sleepSeconds + jitter) * 1000;
}

private getUserAgent(): string {
Expand Down Expand Up @@ -599,7 +610,7 @@ export abstract class AbstractPage<Item> implements AsyncIterable<Item> {
async getNextPage(): Promise<this> {
const nextInfo = this.nextPageInfo();
if (!nextInfo) {
throw new Error(
throw new OpenAIError(
'No next page expected; please check `.hasNextPage()` before calling `.getNextPage()`.',
);
}
Expand Down Expand Up @@ -925,10 +936,10 @@ export const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve

const validatePositiveInteger = (name: string, n: unknown): number => {
if (typeof n !== 'number' || !Number.isInteger(n)) {
throw new Error(`${name} must be an integer`);
throw new OpenAIError(`${name} must be an integer`);
}
if (n < 0) {
throw new Error(`${name} must be a positive integer`);
throw new OpenAIError(`${name} must be a positive integer`);
}
return n;
};
Expand All @@ -939,7 +950,7 @@ export const castToError = (err: any): Error => {
};

export const ensurePresent = <T>(value: T | null | undefined): T => {
if (value == null) throw new Error(`Expected a value to be given but received ${value} instead.`);
if (value == null) throw new OpenAIError(`Expected a value to be given but received ${value} instead.`);
return value;
};

Expand All @@ -962,14 +973,14 @@ export const coerceInteger = (value: unknown): number => {
if (typeof value === 'number') return Math.round(value);
if (typeof value === 'string') return parseInt(value, 10);

throw new Error(`Could not coerce ${value} (type: ${typeof value}) into a number`);
throw new OpenAIError(`Could not coerce ${value} (type: ${typeof value}) into a number`);
};

export const coerceFloat = (value: unknown): number => {
if (typeof value === 'number') return value;
if (typeof value === 'string') return parseFloat(value);

throw new Error(`Could not coerce ${value} (type: ${typeof value}) into a number`);
throw new OpenAIError(`Could not coerce ${value} (type: ${typeof value}) into a number`);
};

export const coerceBoolean = (value: unknown): boolean => {
Expand Down Expand Up @@ -1073,5 +1084,5 @@ export const toBase64 = (str: string | null | undefined): string => {
return btoa(str);
}

throw new Error('Cannot generate b64 string; Expected `Buffer` or `btoa` to be defined');
throw new OpenAIError('Cannot generate b64 string; Expected `Buffer` or `btoa` to be defined');
};
4 changes: 3 additions & 1 deletion src/error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

import { castToError, Headers } from './core';

export class APIError extends Error {
export class OpenAIError extends Error {}

export class APIError extends OpenAIError {
readonly status: number | undefined;
readonly headers: Headers | undefined;
readonly error: Object | undefined;
Expand Down
6 changes: 4 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ export class OpenAI extends Core.APIClient {
...opts
}: ClientOptions = {}) {
if (apiKey === undefined) {
throw new Error(
throw new Errors.OpenAIError(
"The OPENAI_API_KEY environment variable is missing or empty; either provide it, or instantiate the OpenAI client with an apiKey option, like new OpenAI({ apiKey: 'my apiKey' }).",
);
}
Expand All @@ -116,7 +116,7 @@ export class OpenAI extends Core.APIClient {
};

if (!options.dangerouslyAllowBrowser && Core.isRunningInBrowser()) {
throw new Error(
throw new Errors.OpenAIError(
"It looks like you're running in a browser-like environment.\n\nThis is disabled by default, as it risks exposing your secret API credentials to attackers.\nIf you understand the risks and have appropriate mitigations in place,\nyou can set the `dangerouslyAllowBrowser` option to `true`, e.g.,\n\nnew OpenAI({ apiKey, dangerouslyAllowBrowser: true });\n\nhttps://help.openai.com/en/articles/5112595-best-practices-for-api-key-safety\n",
);
}
Expand Down Expand Up @@ -164,6 +164,7 @@ export class OpenAI extends Core.APIClient {

static OpenAI = this;

static OpenAIError = Errors.OpenAIError;
static APIError = Errors.APIError;
static APIConnectionError = Errors.APIConnectionError;
static APIConnectionTimeoutError = Errors.APIConnectionTimeoutError;
Expand All @@ -179,6 +180,7 @@ export class OpenAI extends Core.APIClient {
}

export const {
OpenAIError,
APIError,
APIConnectionError,
APIConnectionTimeoutError,
Expand Down
2 changes: 1 addition & 1 deletion src/resources/chat/completions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ export namespace ChatCompletionChunk {
* content was omitted due to a flag from our content filters, or `function_call`
* if the model called a function.
*/
finish_reason: 'stop' | 'length' | 'function_call' | null;
finish_reason: 'stop' | 'length' | 'function_call' | 'content_filter' | null;

/**
* The index of the choice in the list of choices.
Expand Down
9 changes: 5 additions & 4 deletions src/streaming.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { type Response } from './_shims/index';
import { OpenAIError } from './error';

type Bytes = string | ArrayBuffer | Uint8Array | Buffer | null | undefined;

Expand All @@ -23,7 +24,7 @@ export class Stream<Item> implements AsyncIterable<Item> {
private async *iterMessages(): AsyncGenerator<ServerSentEvent, void, unknown> {
if (!this.response.body) {
this.controller.abort();
throw new Error(`Attempted to iterate over a response with no body`);
throw new OpenAIError(`Attempted to iterate over a response with no body`);
}
const lineDecoder = new LineDecoder();

Expand Down Expand Up @@ -198,7 +199,7 @@ class LineDecoder {
return Buffer.from(bytes).toString();
}

throw new Error(
throw new OpenAIError(
`Unexpected: received non-Uint8Array (${bytes.constructor.name}) stream chunk in an environment with a global "Buffer" defined, which this library assumes to be Node. Please report this error.`,
);
}
Expand All @@ -210,14 +211,14 @@ class LineDecoder {
return this.textDecoder.decode(bytes);
}

throw new Error(
throw new OpenAIError(
`Unexpected: received non-Uint8Array/ArrayBuffer (${
(bytes as any).constructor.name
}) in a web platform. Please report this error.`,
);
}

throw new Error(
throw new OpenAIError(
`Unexpected: neither Buffer nor TextDecoder are available as globals. Please report this error.`,
);
}
Expand Down
2 changes: 1 addition & 1 deletion src/version.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export const VERSION = '4.10.0'; // x-release-please-version
export const VERSION = '4.11.0'; // x-release-please-version
Loading

0 comments on commit eddb247

Please sign in to comment.