Skip to content

Commit

Permalink
Merge pull request #245 from openai/next
Browse files Browse the repository at this point in the history
chore: unreleased changes
  • Loading branch information
schnerd committed Aug 27, 2023
2 parents 2d646c3 + 6d8cff0 commit abdc4ed
Show file tree
Hide file tree
Showing 8 changed files with 188 additions and 64 deletions.
40 changes: 40 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,15 @@ Error codes are as followed:
| >=500 | `InternalServerError` |
| N/A | `APIConnectionError` |

### Azure OpenAI

An example of using this library with Azure OpenAI can be found [here](https://github.com/openai/openai-node/blob/master/examples/azure.ts).

Please note there are subtle differences in API shape & behavior between the Azure OpenAI API and the OpenAI API,
so using this library with Azure OpenAI may result in incorrect types, which can lead to bugs.

See [`@azure/openai`](https://www.npmjs.com/package/@azure/openai) for an Azure-specific SDK provided by Microsoft.

### Retries

Certain errors will be automatically retried 2 times by default, with a short exponential backoff.
Expand Down Expand Up @@ -206,6 +215,37 @@ On timeout, an `APIConnectionTimeoutError` is thrown.

Note that requests which time out will be [retried twice by default](#retries).

## Auto-pagination

List methods in the OpenAI API are paginated.
You can use `for await … of` syntax to iterate through items across all pages:

```ts
async function fetchAllFineTuningJobs(params) {
const allFineTuningJobs = [];
// Automatically fetches more pages as needed.
for await (const job of openai.fineTuning.jobs.list({ limit: 20 })) {
allFineTuningJobs.push(job);
}
return allFineTuningJobs;
}
```

Alternatively, you can make request a single page at a time:

```ts
let page = await openai.fineTuning.jobs.list({ limit: 20 });
for (const job of page.data) {
console.log(job);
}

// Convenience methods are provided for manually paginating:
while (page.hasNextPage()) {
page = page.getNextPage();
// ...
}
```

## Advanced Usage

### Accessing raw Response data (e.g., headers)
Expand Down
70 changes: 49 additions & 21 deletions bin/cli
Original file line number Diff line number Diff line change
@@ -1,21 +1,49 @@
#!/usr/bin/env bash
set -eou pipefail

if [ $# -eq 0 ]; then
echo "Usage: $0 <subcommand>"
echo
echo "Subcommands:"
echo " migrate Run migrations to update from openai v3 to v4"
echo
exit 1
fi

if [ "$1" = "migrate" ]; then
echo "This automatic code migration is provided by grit.io"
echo "Visit https://app.grit.io/studio?preset=openai_v4 for more details."
shift
npx -y @getgrit/launcher apply openai_v4 "$@"
else
echo "Unknown subcommand $1; Expected 'migrate'" >&2
exit 1
fi
#!/usr/bin/env node

const { spawnSync } = require('child_process');

const commands = {
migrate: {
description: 'Run migrations to update from openai v3 to v4',
fn: () => {
console.log('This automatic code migration is provided by grit.io');
console.log('Visit https://app.grit.io/studio?preset=openai_v4 for more details.')

const result = spawnSync(
'npx',
['-y', '@getgrit/launcher', 'apply', 'openai_v4', ...process.argv.slice(3)],
{ stdio: 'inherit' },
);
if (result.status !== 0) {
process.exit(result.status);
}
}
}
}

function exitWithHelp() {
console.log("Usage: $0 <subcommand>");
console.log();
console.log('Subcommands:');

for (const [name, info] of Object.entries(commands)) {
console.log(` ${name} ${info.description}`);
}

console.log();
process.exit(1);
}

if (process.argv.length < 3) {
exitWithHelp();
}

const commandName = process.argv[2];

const command = commands[commandName];
if (!command) {
console.log(`Unknown subcommand ${commandName}.`);
exitWithHelp();
}

command.fn();
5 changes: 4 additions & 1 deletion examples/azure.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ const resource = '<your resource name>';
// Navigate to the Azure OpenAI Studio to deploy a model.
const model = '<your model>';

// https://learn.microsoft.com/en-us/azure/ai-services/openai/reference#rest-api-versioning
const apiVersion = '2023-06-01-preview';

const apiKey = process.env['AZURE_OPENAI_API_KEY'];
if (!apiKey) {
throw new Error('The AZURE_OPENAI_API_KEY environment variable is missing or empty.');
Expand All @@ -19,7 +22,7 @@ if (!apiKey) {
const openai = new OpenAI({
apiKey,
baseURL: `https://${resource}.openai.azure.com/openai/deployments/${model}`,
defaultQuery: { 'api-version': '2023-06-01-preview' },
defaultQuery: { 'api-version': apiVersion },
defaultHeaders: { 'api-key': apiKey },
});

Expand Down
24 changes: 18 additions & 6 deletions src/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
type RequestInfo,
type RequestInit,
type Response,
type HeadersInit,
} from 'openai/_shims/fetch';
export { type Response };
import { isMultipartBody } from './uploads';
Expand Down Expand Up @@ -153,7 +154,7 @@ export abstract class APIClient {
this.fetch = overridenFetch ?? fetch;
}

protected authHeaders(): Headers {
protected authHeaders(opts: FinalRequestOptions): Headers {
return {};
}

Expand All @@ -165,13 +166,13 @@ export abstract class APIClient {
* Authorization: 'Bearer 123',
* }
*/
protected defaultHeaders(): Headers {
protected defaultHeaders(opts: FinalRequestOptions): Headers {
return {
Accept: 'application/json',
'Content-Type': 'application/json',
'User-Agent': this.getUserAgent(),
...getPlatformHeaders(),
...this.authHeaders(),
...this.authHeaders(opts),
};
}

Expand Down Expand Up @@ -272,7 +273,7 @@ export abstract class APIClient {

const reqHeaders: Record<string, string> = {
...(contentLength && { 'Content-Length': contentLength }),
...this.defaultHeaders(),
...this.defaultHeaders(options),
...headers,
};
// let builtin fetch set the Content-Type for multipart bodies
Expand Down Expand Up @@ -304,7 +305,18 @@ export abstract class APIClient {
* This is useful for cases where you want to add certain headers based off of
* the request properties, e.g. `method` or `url`.
*/
protected async prepareRequest(request: RequestInit, { url }: { url: string }): Promise<void> {}
protected async prepareRequest(
request: RequestInit,
{ url, options }: { url: string; options: FinalRequestOptions },
): Promise<void> {}

protected parseHeaders(headers: HeadersInit | null | undefined): Record<string, string> {
return (
!headers ? {}
: Symbol.iterator in headers ? Object.fromEntries(Array.from(headers).map((header) => [...header]))
: { ...headers }
);
}

protected makeStatusError(
status: number | undefined,
Expand Down Expand Up @@ -333,7 +345,7 @@ export abstract class APIClient {

const { req, url, timeout } = this.buildRequest(options);

await this.prepareRequest(req, { url });
await this.prepareRequest(req, { url, options });

debug('request', url, options, req.headers);

Expand Down
10 changes: 7 additions & 3 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,15 +150,15 @@ export class OpenAI extends Core.APIClient {
return this._options.defaultQuery;
}

protected override defaultHeaders(): Core.Headers {
protected override defaultHeaders(opts: Core.FinalRequestOptions): Core.Headers {
return {
...super.defaultHeaders(),
...super.defaultHeaders(opts),
'OpenAI-Organization': this.organization,
...this._options.defaultHeaders,
};
}

protected override authHeaders(): Core.Headers {
protected override authHeaders(opts: Core.FinalRequestOptions): Core.Headers {
return { Authorization: `Bearer ${this.apiKey}` };
}

Expand Down Expand Up @@ -206,6 +206,10 @@ export namespace OpenAI {
export import Page = Pagination.Page;
export import PageResponse = Pagination.PageResponse;

export import CursorPage = Pagination.CursorPage;
export import CursorPageParams = Pagination.CursorPageParams;
export import CursorPageResponse = Pagination.CursorPageResponse;

export import Completions = API.Completions;
export import Completion = API.Completion;
export import CompletionChoice = API.CompletionChoice;
Expand Down
60 changes: 59 additions & 1 deletion src/pagination.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// File generated from our OpenAPI spec by Stainless.

import { AbstractPage, Response, APIClient, FinalRequestOptions } from './core';
import { AbstractPage, Response, APIClient, FinalRequestOptions, PageInfo } from './core';

export interface PageResponse<Item> {
data: Array<Item>;
Expand Down Expand Up @@ -40,3 +40,61 @@ export class Page<Item> extends AbstractPage<Item> implements PageResponse<Item>
return null;
}
}

export interface CursorPageResponse<Item> {
data: Array<Item>;
}

export interface CursorPageParams {
/**
* Identifier for the last job from the previous pagination request.
*/
after?: string;

/**
* Number of fine-tuning jobs to retrieve.
*/
limit?: number;
}

export class CursorPage<Item extends { id: string }>
extends AbstractPage<Item>
implements CursorPageResponse<Item>
{
data: Array<Item>;

constructor(
client: APIClient,
response: Response,
body: CursorPageResponse<Item>,
options: FinalRequestOptions,
) {
super(client, response, body, options);

this.data = body.data;
}

getPaginatedItems(): Item[] {
return this.data;
}

// @deprecated Please use `nextPageInfo()` instead
nextPageParams(): Partial<CursorPageParams> | null {
const info = this.nextPageInfo();
if (!info) return null;
if ('params' in info) return info.params;
const params = Object.fromEntries(info.url.searchParams);
if (!Object.keys(params).length) return null;
return params;
}

nextPageInfo(): PageInfo | null {
if (!this.data?.length) {
return null;
}

const next = this.data[this.data.length - 1]?.id;
if (!next) return null;
return { params: { after: next } };
}
}
36 changes: 5 additions & 31 deletions src/resources/fine-tuning/jobs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { APIResource } from 'openai/resource';
import { isRequestOptions } from 'openai/core';
import * as Files from 'openai/resources/files';
import * as API from './index';
import { Page } from 'openai/pagination';
import { CursorPage, CursorPageParams } from 'openai/pagination';

export class Jobs extends APIResource {
/**
Expand Down Expand Up @@ -81,17 +81,11 @@ export class Jobs extends APIResource {
}
}

/**
* Note: no pagination actually occurs yet, this is for forwards-compatibility.
*/
export class FineTuningJobsPage extends Page<FineTuningJob> {}
export class FineTuningJobsPage extends CursorPage<FineTuningJob> {}
// alias so we can export it in the namespace
type _FineTuningJobsPage = FineTuningJobsPage;

/**
* Note: no pagination actually occurs yet, this is for forwards-compatibility.
*/
export class FineTuningJobEventsPage extends Page<FineTuningJobEvent> {}
export class FineTuningJobEventsPage extends CursorPage<FineTuningJobEvent> {}
// alias so we can export it in the namespace
type _FineTuningJobEventsPage = FineTuningJobEventsPage;

Expand Down Expand Up @@ -258,29 +252,9 @@ export namespace JobCreateParams {
}
}

export interface JobListParams {
/**
* Identifier for the last job from the previous pagination request.
*/
after?: string;

/**
* Number of fine-tuning jobs to retrieve.
*/
limit?: number;
}

export interface JobListEventsParams {
/**
* Identifier for the last event from the previous pagination request.
*/
after?: string;
export interface JobListParams extends CursorPageParams {}

/**
* Number of events to retrieve.
*/
limit?: number;
}
export interface JobListEventsParams extends CursorPageParams {}

export namespace Jobs {
export import FineTuningJob = API.FineTuningJob;
Expand Down
7 changes: 6 additions & 1 deletion src/streaming.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,14 @@ type ServerSentEvent = {
};

export class Stream<Item> implements AsyncIterable<Item> {
controller: AbortController;

private response: Response;
private decoder: SSEDecoder;

constructor(private response: Response, private controller: AbortController) {
constructor(response: Response, controller: AbortController) {
this.response = response;
this.controller = controller;
this.decoder = new SSEDecoder();
}

Expand Down

0 comments on commit abdc4ed

Please sign in to comment.