Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.40.2"
".": "4.41.0"
}
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Changelog

## 4.41.0 (2024-05-05)

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

### Features

* **client:** add Azure client ([#822](https://github.com/openai/openai-node/issues/822)) ([92f9049](https://github.com/openai/openai-node/commit/92f90499f0bbee79ba9c8342c8d58dbcaf88bdd1))

## 4.40.2 (2024-05-03)

Full Changelog: [v4.40.1...v4.40.2](https://github.com/openai/openai-node/compare/v4.40.1...v4.40.2)
Expand Down
23 changes: 17 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ You can import in Deno via:
<!-- x-release-please-start-version -->

```ts
import OpenAI from 'https://deno.land/x/openai@v4.40.2/mod.ts';
import OpenAI from 'https://deno.land/x/openai@v4.41.0/mod.ts';
```

<!-- x-release-please-end -->
Expand Down Expand Up @@ -361,14 +361,25 @@ Error codes are as followed:
| >=500 | `InternalServerError` |
| N/A | `APIConnectionError` |

### Azure OpenAI
## Microsoft 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).
To use this library with [Azure OpenAI](https://learn.microsoft.com/en-us/azure/ai-services/openai/overview), use the `AzureOpenAI`
class instead of the `OpenAI` class.

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.
> [!IMPORTANT]
> The Azure API shape differs from the core API shape which means that the static types for responses / params
> won't always be correct.

```ts
const openai = new AzureOpenAI();

See [`@azure/openai`](https://www.npmjs.com/package/@azure/openai) for an Azure-specific SDK provided by Microsoft.
const result = await openai.chat.completions.create({
model: 'gpt-4-1106-preview',
messages: [{ role: 'user', content: 'Say hello!' }],
});

console.log(result.choices[0]!.message?.content);
```

### Retries

Expand Down
32 changes: 8 additions & 24 deletions examples/azure.ts
Original file line number Diff line number Diff line change
@@ -1,43 +1,27 @@
#!/usr/bin/env -S npm run tsn -T

import OpenAI from 'openai';
import { AzureOpenAI } from 'openai';

// The name of your Azure OpenAI Resource.
// https://learn.microsoft.com/en-us/azure/cognitive-services/openai/how-to/create-resource?pivots=web-portal#create-a-resource
const resource = '<your resource name>';

// Corresponds to your Model deployment within your OpenAI resource, e.g. my-gpt35-16k-deployment
// Corresponds to your Model deployment within your OpenAI resource, e.g. gpt-4-1106-preview
// 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 deployment = 'gpt-4-1106-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.');
}

// Azure OpenAI requires a custom baseURL, api-version query param, and api-key header.
const openai = new OpenAI({
apiKey,
baseURL: `https://${resource}.openai.azure.com/openai/deployments/${model}`,
defaultQuery: { 'api-version': apiVersion },
defaultHeaders: { 'api-key': apiKey },
});
// Make sure to set both AZURE_OPENAI_ENDPOINT with the endpoint of your Azure resource and AZURE_OPENAI_API_KEY with the API key.
// You can find both information in the Azure Portal.
const openai = new AzureOpenAI();

async function main() {
console.log('Non-streaming:');
const result = await openai.chat.completions.create({
model,
model: deployment,
messages: [{ role: 'user', content: 'Say hello!' }],
});
console.log(result.choices[0]!.message?.content);

console.log();
console.log('Streaming:');
const stream = await openai.chat.completions.create({
model,
model: deployment,
messages: [{ role: 'user', content: 'Say hello!' }],
stream: true,
});
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.40.2",
"version": "4.41.0",
"description": "The official TypeScript library for the OpenAI API",
"author": "OpenAI <support@openai.com>",
"types": "dist/index.d.ts",
Expand Down
2 changes: 1 addition & 1 deletion scripts/build-deno
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ This is a build produced from https://github.com/openai/openai-node – please g
Usage:

\`\`\`ts
import OpenAI from "https://deno.land/x/openai@v4.40.2/mod.ts";
import OpenAI from "https://deno.land/x/openai@v4.41.0/mod.ts";

const client = new OpenAI();
\`\`\`
Expand Down
185 changes: 184 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import * as Core from './core';
import * as Errors from './error';
import { type Agent } from './_shims/index';
import { type Agent, type RequestInit } from './_shims/index';
import * as Uploads from './uploads';
import * as Pagination from 'openai/pagination';
import * as API from 'openai/resources/index';
Expand Down Expand Up @@ -310,4 +310,187 @@ export namespace OpenAI {
export import FunctionParameters = API.FunctionParameters;
}

// ---------------------- Azure ----------------------

/** API Client for interfacing with the Azure OpenAI API. */
export interface AzureClientOptions extends ClientOptions {
/**
* Defaults to process.env['OPENAI_API_VERSION'].
*/
apiVersion?: string | undefined;

/**
* Your Azure endpoint, including the resource, e.g. `https://example-resource.azure.openai.com/`
*/
endpoint?: string | undefined;

/**
* A model deployment, if given, sets the base client URL to include `/deployments/{deployment}`.
* Note: this means you won't be able to use non-deployment endpoints. Not supported with Assistants APIs.
*/
deployment?: string | undefined;

/**
* Defaults to process.env['AZURE_OPENAI_API_KEY'].
*/
apiKey?: string | undefined;

/**
* A function that returns an access token for Microsoft Entra (formerly known as Azure Active Directory),
* which will be invoked on every request.
*/
azureADTokenProvider?: (() => string) | undefined;
}

/** API Client for interfacing with the Azure OpenAI API. */
export class AzureOpenAI extends OpenAI {
private _azureADTokenProvider: (() => string) | undefined;
apiVersion: string = '';
/**
* API Client for interfacing with the Azure OpenAI API.
*
* @param {string | undefined} [opts.apiVersion=process.env['OPENAI_API_VERSION'] ?? undefined]
* @param {string | undefined} [opts.endpoint=process.env['AZURE_OPENAI_ENDPOINT'] ?? undefined] - Your Azure endpoint, including the resource, e.g. `https://example-resource.azure.openai.com/`
* @param {string | undefined} [opts.apiKey=process.env['AZURE_OPENAI_API_KEY'] ?? undefined]
* @param {string | undefined} opts.deployment - A model deployment, if given, sets the base client URL to include `/deployments/{deployment}`.
* @param {string | null | undefined} [opts.organization=process.env['OPENAI_ORG_ID'] ?? null]
* @param {string} [opts.baseURL=process.env['OPENAI_BASE_URL']] - Sets the base URL for the API.
* @param {number} [opts.timeout=10 minutes] - The maximum amount of time (in milliseconds) the client will wait for a response before timing out.
* @param {number} [opts.httpAgent] - An HTTP agent used to manage HTTP(s) connections.
* @param {Core.Fetch} [opts.fetch] - Specify a custom `fetch` function implementation.
* @param {number} [opts.maxRetries=2] - The maximum number of times the client will retry a request.
* @param {Core.Headers} opts.defaultHeaders - Default headers to include with every request to the API.
* @param {Core.DefaultQuery} opts.defaultQuery - Default query parameters to include with every request to the API.
* @param {boolean} [opts.dangerouslyAllowBrowser=false] - By default, client-side use of this library is not allowed, as it risks exposing your secret API credentials to attackers.
*/
constructor({
baseURL = Core.readEnv('OPENAI_BASE_URL'),
apiKey = Core.readEnv('AZURE_OPENAI_API_KEY'),
apiVersion = Core.readEnv('OPENAI_API_VERSION'),
endpoint,
deployment,
azureADTokenProvider,
dangerouslyAllowBrowser,
...opts
}: AzureClientOptions = {}) {
if (!apiVersion) {
throw new Errors.OpenAIError(
"The OPENAI_API_VERSION environment variable is missing or empty; either provide it, or instantiate the AzureOpenAI client with an apiVersion option, like new AzureOpenAI({ apiVersion: 'My API Version' }).",
);
}

if (typeof azureADTokenProvider === 'function') {
dangerouslyAllowBrowser = true;
}

if (!azureADTokenProvider && !apiKey) {
throw new Errors.OpenAIError(
'Missing credentials. Please pass one of `apiKey` and `azureADTokenProvider`, or set the `AZURE_OPENAI_API_KEY` environment variable.',
);
}

if (azureADTokenProvider && apiKey) {
throw new Errors.OpenAIError(
'The `apiKey` and `azureADTokenProvider` arguments are mutually exclusive; only one can be passed at a time.',
);
}

// define a sentinel value to avoid any typing issues
apiKey ??= API_KEY_SENTINEL;

opts.defaultQuery = { ...opts.defaultQuery, 'api-version': apiVersion };

if (!baseURL) {
if (!endpoint) {
endpoint = process.env['AZURE_OPENAI_ENDPOINT'];
}

if (!endpoint) {
throw new Errors.OpenAIError(
'Must provide one of the `baseURL` or `endpoint` arguments, or the `AZURE_OPENAI_ENDPOINT` environment variable',
);
}

if (deployment) {
baseURL = `${endpoint}/openai/deployments/${deployment}`;
} else {
baseURL = `${endpoint}/openai`;
}
} else {
if (endpoint) {
throw new Errors.OpenAIError('baseURL and endpoint are mutually exclusive');
}
}

super({
apiKey,
baseURL,
...opts,
...(dangerouslyAllowBrowser !== undefined ? { dangerouslyAllowBrowser } : {}),
});

this._azureADTokenProvider = azureADTokenProvider;
this.apiVersion = apiVersion;
}

override buildRequest(options: Core.FinalRequestOptions<unknown>): {
req: RequestInit;
url: string;
timeout: number;
} {
if (_deployments_endpoints.has(options.path) && options.method === 'post' && options.body !== undefined) {
if (!Core.isObj(options.body)) {
throw new Error('Expected request body to be an object');
}
const model = options.body['model'];
delete options.body['model'];
if (model !== undefined && !this.baseURL.includes('/deployments')) {
options.path = `/deployments/${model}${options.path}`;
}
}
return super.buildRequest(options);
}

private _getAzureADToken(): string | undefined {
if (typeof this._azureADTokenProvider === 'function') {
const token = this._azureADTokenProvider();
if (!token || typeof token !== 'string') {
throw new Errors.OpenAIError(
`Expected 'azureADTokenProvider' argument to return a string but it returned ${token}`,
);
}
return token;
}
return undefined;
}

protected override authHeaders(opts: Core.FinalRequestOptions): Core.Headers {
if (opts.headers?.['Authorization'] || opts.headers?.['api-key']) {
return {};
}
const token = this._getAzureADToken();
if (token) {
return { Authorization: `Bearer ${token}` };
}
if (this.apiKey !== API_KEY_SENTINEL) {
return { 'api-key': this.apiKey };
}
throw new Errors.OpenAIError('Unable to handle auth');
}
}

const _deployments_endpoints = new Set([
'/completions',
'/chat/completions',
'/embeddings',
'/audio/transcriptions',
'/audio/translations',
'/audio/speech',
'/images/generations',
]);

const API_KEY_SENTINEL = '<Missing Key>';

// ---------------------- End Azure ----------------------

export default OpenAI;
1 change: 1 addition & 0 deletions src/resources/beta/vector-stores/files.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ export class Files extends APIResource {

/**
* Upload a file to the `files` API and then attach it to the given vector store.
*
* Note the file will be asynchronously processed (you can use the alternative
* polling helper method to wait for processing to complete).
*/
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.40.2'; // x-release-please-version
export const VERSION = '4.41.0'; // x-release-please-version
Loading