Skip to content

Commit

Permalink
Merge pull request #3 from lino-levan/master
Browse files Browse the repository at this point in the history
feat: add whisper transcription and translation
  • Loading branch information
lino-levan authored Mar 20, 2023
2 parents 5937e54 + 69a7477 commit a779b21
Show file tree
Hide file tree
Showing 6 changed files with 117 additions and 30 deletions.
6 changes: 5 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
name: ci

on: [push, pull_request]
on:
push:
branches: [master]
pull_request:
branches: [master]

jobs:
build:
Expand Down
File renamed without changes.
Binary file added examples/testdata/jfk.wav
Binary file not shown.
10 changes: 10 additions & 0 deletions examples/transcription.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { OpenAI } from "../mod.ts";

const openAI = new OpenAI("YOUR_API_KEY");

const transcription = await openAI.createTranscription({
model: "whisper-1",
file: "./testdata/jfk.wav", // TODO: Do this more portably
});

console.log(transcription);
125 changes: 98 additions & 27 deletions src/openai.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import type {
Translation,
TranslationOptions,
} from "./types.ts";
import { basename } from "https://deno.land/std@0.180.0/path/mod.ts";

const baseUrl = "https://api.openai.com/v1";

Expand All @@ -38,15 +39,25 @@ export class OpenAI {
this.#privateKey = privateKey;
}

// deno-lint-ignore no-explicit-any
async #request(url: string, body: any, method = "POST") {
async #request(
url: string,
// deno-lint-ignore no-explicit-any
body: any,
options?: { method?: string; noContentType?: boolean },
) {
const response = await fetch(`${baseUrl}${url}`, {
body: body ? JSON.stringify(body) : undefined,
body: options?.noContentType
? body
: (body ? JSON.stringify(body) : undefined),
headers: {
Authorization: `Bearer ${this.#privateKey}`,
"Content-Type": "application/json",
...(
options?.noContentType ? {} : {
"Content-Type": "application/json",
}
),
},
method,
method: options?.method ?? "POST",
});

return await response.json();
Expand All @@ -58,7 +69,7 @@ export class OpenAI {
* https://platform.openai.com/docs/api-reference/models/list
*/
async listModels(): Promise<ModelList> {
return await this.#request("/models", undefined, "GET");
return await this.#request("/models", undefined, { method: "GET" });
}

/**
Expand All @@ -67,7 +78,9 @@ export class OpenAI {
* https://platform.openai.com/docs/api-reference/models/retrieve
*/
async getModel(model: string): Promise<Model> {
return await this.#request(`/models/${model}`, undefined, "GET");
return await this.#request(`/models/${model}`, undefined, {
method: "GET",
});
}

/**
Expand Down Expand Up @@ -201,13 +214,39 @@ export class OpenAI {
async createTranscription(
options: TranscriptionOptions,
): Promise<Transcription> {
return await this.#request(`/audio/transcriptions`, {
file: options.file,
model: options.model,
prompt: options.prompt,
response_format: options.responseFormat,
temperature: options.temperature,
language: options.language,
const formData = new FormData();

// Model specified
formData.append("model", options.model);

// File data
if (typeof options.file === "string") {
const file = await Deno.readFile(options.file);

formData.append(
"file",
new File([file], basename(options.file)),
);
} else {
// Deno types are wrong
formData.append("file", options.file as unknown as Blob);
}

if (options.prompt) {
formData.append("prompt", options.prompt);
}
if (options.responseFormat) {
formData.append("response_format", options.responseFormat);
}
if (options.temperature) {
formData.append("temperature", options.temperature.toString());
}
if (options.language) {
formData.append("language", options.language);
}

return await this.#request(`/audio/transcriptions`, formData, {
noContentType: true,
});
}

Expand All @@ -217,12 +256,36 @@ export class OpenAI {
* https://platform.openai.com/docs/api-reference/audio/create
*/
async createTranslation(options: TranslationOptions): Promise<Translation> {
return await this.#request(`/audio/translations`, {
file: options.file,
model: options.model,
prompt: options.prompt,
response_format: options.responseFormat,
temperature: options.temperature,
const formData = new FormData();

// Model specified
formData.append("model", options.model);

// File data
if (typeof options.file === "string") {
const file = await Deno.readFile(options.file);

formData.append(
"file",
new File([file], basename(options.file)),
);
} else {
// Deno types are wrong
formData.append("file", options.file as unknown as Blob);
}

if (options.prompt) {
formData.append("prompt", options.prompt);
}
if (options.responseFormat) {
formData.append("response_format", options.responseFormat);
}
if (options.temperature) {
formData.append("temperature", options.temperature.toString());
}

return await this.#request(`/audio/translations`, formData, {
noContentType: true,
});
}

Expand All @@ -232,7 +295,7 @@ export class OpenAI {
* https://platform.openai.com/docs/api-reference/files/list
*/
async listFiles(): Promise<FileList> {
return await this.#request(`/files`, undefined, "GET");
return await this.#request(`/files`, undefined, { method: "GET" });
}

/**
Expand All @@ -253,7 +316,9 @@ export class OpenAI {
* https://platform.openai.com/docs/api-reference/files/delete
*/
async deleteFile(fileId: string): Promise<DeletedFile> {
return await this.#request(`/files/${fileId}`, undefined, "DELETE");
return await this.#request(`/files/${fileId}`, undefined, {
method: "DELETE",
});
}

/**
Expand All @@ -262,7 +327,9 @@ export class OpenAI {
* https://platform.openai.com/docs/api-reference/files/retrieve
*/
async retrieveFile(fileId: string): Promise<File> {
return await this.#request(`/files/${fileId}`, undefined, "GET");
return await this.#request(`/files/${fileId}`, undefined, {
method: "GET",
});
}

/**
Expand Down Expand Up @@ -310,7 +377,7 @@ export class OpenAI {
* https://platform.openai.com/docs/api-reference/fine-tunes/list
*/
async listFineTunes(): Promise<FineTuneList> {
return await this.#request(`/fine-tunes`, undefined, "GET");
return await this.#request(`/fine-tunes`, undefined, { method: "GET" });
}

/**
Expand All @@ -321,7 +388,9 @@ export class OpenAI {
async retrieveFineTune(
fineTuneId: string,
): Promise<(FineTune & { events: FineTuneEvent[] })> {
return await this.#request(`/fine-tunes/${fineTuneId}`, undefined, "GET");
return await this.#request(`/fine-tunes/${fineTuneId}`, undefined, {
method: "GET",
});
}

/**
Expand All @@ -345,7 +414,7 @@ export class OpenAI {
return await this.#request(
`/fine-tunes/${fineTuneId}/events`,
undefined,
"GET",
{ method: "GET" },
);
}

Expand All @@ -355,7 +424,9 @@ export class OpenAI {
* https://platform.openai.com/docs/api-reference/fine-tunes/delete-model
*/
async deleteFineTuneModel(model: string): Promise<DeletedFineTune> {
return await this.#request(`/models/${model}`, undefined, "DELETE");
return await this.#request(`/models/${model}`, undefined, {
method: "DELETE",
});
}

/**
Expand Down
6 changes: 4 additions & 2 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export type FileSpecifier = string | File;

export interface CompletionOptions {
/**
* ID of the model to use. You can use the List models API to see all of your available models, or see our Model overview for descriptions of them.
Expand Down Expand Up @@ -388,7 +390,7 @@ export interface TranscriptionOptions {
* The audio file to transcribe, in one of these formats: mp3, mp4, mpeg, mpga, m4a, wav, or webm.
* https://platform.openai.com/docs/api-reference/audio/create#audio/create-file
*/
file: string;
file: FileSpecifier;

/**
* ID of the model to use. Only whisper-1 is currently available.
Expand Down Expand Up @@ -427,7 +429,7 @@ export interface TranslationOptions {
* The audio file to translate, in one of these formats: mp3, mp4, mpeg, mpga, m4a, wav, or webm.
* https://platform.openai.com/docs/api-reference/audio/create#audio/create-file
*/
file: string;
file: FileSpecifier;

/**
* ID of the model to use. Only whisper-1 is currently available.
Expand Down

0 comments on commit a779b21

Please sign in to comment.