Skip to content

Commit

Permalink
tweaks
Browse files Browse the repository at this point in the history
  • Loading branch information
eladb committed Mar 19, 2024
1 parent 4767791 commit 6aebb61
Show file tree
Hide file tree
Showing 9 changed files with 158 additions and 63 deletions.
54 changes: 44 additions & 10 deletions openai/README.md
Original file line number Diff line number Diff line change
@@ -1,38 +1,72 @@
# openai

An [OpenAI](https://openai.com) library for Winglang.

> This is an initial version of this library which currently exposes a very small subset of the
> OpenAI API.
## Prerequisites

* [winglang](https://winglang.io).

## Installation

`sh
```sh
npm i @winglibs/openai
`
```

## Example

`js
```js
bring cloud;
bring openai;

let openAIService = new openai.OpenAI("your-api-key");
let key = new cloud.Secret(name: "OAIApiKey");
let oai = new openai.OpenAI(apiKeySecret: key);

new cloud.Function(inflight () => {
let joke = openAIService.createCompletion("tell me a short joke");
let joke = oai.createCompletion("tell me a short joke", model: "gpt-3.5-turbo", max_tokens: 2048);
log(joke);
});
`
```

When running in a `test` context, the `createCompletion` method will return a JSON object which
echos the request under the `mock` key:

```js
bring expect;

test "create completion test" {
let r = oai.createCompletion("tell me a short joke");
expect.equal(r, Json.stringify({
mock: {
prompt:"tell me a short joke",
params:{"model":"gpt-3.5-turbo","max_tokens":2048}
}
}));
}
```

## Usage
The `openai.OpenAI` resource provides the following inflight methods:

* `createCompletion` - Gets an answer to a prompt.
```js
new openai.OpenAI();
```

* `apiKeySecret` - a `cloud.Secret` with the OpenAI API key (required).
* `orgSecret` - a `cloud.Secret` with the OpenAI organization ID (not required).

You can also specify clear text values through `apiKey` and `org`, but make sure not to commit these
values to a repository :warning:.

Methods:

The preflight constructor requires an api key in the form of either a secret or a string.
* `inflight createCompletion()` - requests a completion from a model. Options are `model` (defaults
to `gpt-3.5.turbo`) and `max_tokens` (defaults to 2048).

## Roadmap

* [x] Support the rest of the openai API
* [ ] Support the rest of the OpenAI API
* [ ] Add more examples
* [ ] Add more tests

Expand Down
2 changes: 1 addition & 1 deletion openai/api.w
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ pub struct CompletionParams {
}

// TODO: need to recreate the openai interface with higher fidelity
pub interface IOpenAI extends std.IResource {
pub interface IOpenAI {
inflight createCompletion(prompt: str, params: CompletionParams?): str;
}
12 changes: 12 additions & 0 deletions openai/example.main.w
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
bring expect;
bring "./openai.w" as openai;

bring cloud;

let key = new cloud.Secret(name: "my-openai-key");
let oai = new openai.OpenAI(apiKeySecret: key);

new cloud.Function(inflight () => {
let answer = oai.createCompletion("tell me a short joke", model: "gpt-3.5-turbo", max_tokens: 2048);
log(answer);
}) as "tell me a joke";
10 changes: 10 additions & 0 deletions openai/openai.extern.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export default interface extern {
createNewInflightClient: (apiKey: string, org?: (string) | undefined) => Promise<IOpenAI$Inflight>,
}
export interface CompletionParams {
readonly max_tokens: number;
readonly model: string;
}
export interface IOpenAI$Inflight {
readonly createCompletion: (prompt: string, params?: (CompletionParams) | undefined) => Promise<string>;
}
10 changes: 3 additions & 7 deletions openai/openai.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
const openai = require('openai');

exports.createNewInflightClient = (apiKey, org) => {
const config = {
apiKey: apiKey
};
const config = { apiKey };

if (org) {
config.organization = org;
Expand All @@ -14,7 +12,7 @@ exports.createNewInflightClient = (apiKey, org) => {
// TODO: this is a hack for now, we should model the openai api in the api.w file with more fidelity
// and then we can just return the client itself, like we do in redis
return {
createCompletion: async (prompt, params = { model: "gpt-3.5-turbo", max_tokens: 2048 }) => {
createCompletion: async (prompt, params = {}) => {
if (!prompt) {
throw new Error("Prompt is required");
};
Expand All @@ -31,12 +29,10 @@ exports.createNewInflightClient = (apiKey, org) => {
params.max_tokens = 2048;
}

params.messages = [{role: 'user', content: prompt}];
params.messages = [ { role: 'user', content: prompt } ];

const response = await client.chat.completions.create(params);

return response.choices[0]?.message?.content;
}
};
};

30 changes: 14 additions & 16 deletions openai/openai.test.w
Original file line number Diff line number Diff line change
@@ -1,21 +1,19 @@
bring expect;
bring "./openai.w" as openai;

let client = new openai.OpenAI();
bring cloud;

// This test currently doesn't pass because of issue https://github.com/winglang/wing/issues/5948
// test "cant create client without key" {
// let var errorMessage = "";
// try {
// let answer = client.createCompletion("tell me a short joke");
// } catch e {
// errorMessage = e;
// }
// expect.equal(errorMessage, "OpenAI API key is required");
// }
let key = new cloud.Secret(name: "my-openai-key");
let oai = new openai.OpenAI(apiKeySecret: key);

// This test currently cannot pass because we can't pass credentials to it in the test runner
// test "basic completion" {
// let answer = client.createCompletion("tell me a short joke");
// expect.notNil(answer);
// }
test "basic completion" {
let answer = oai.createCompletion("tell me a short joke", model: "gpt-3.5-turbo", max_tokens: 2048);

// in tests, the response is just an echo of the request
expect.equal(answer, Json.stringify({
mock: {
prompt:"tell me a short joke",
params:{"model":"gpt-3.5-turbo","max_tokens":2048}
}
}));
}
53 changes: 34 additions & 19 deletions openai/openai.w
Original file line number Diff line number Diff line change
Expand Up @@ -3,40 +3,55 @@ bring "./api.w" as api;
bring "./utils.w" as utils;
bring cloud;

pub struct OpenAIProps {
apiKey: str?;
org: str?;
apiKeySecret: cloud.Secret?;
orgSecret: cloud.Secret?;
}

inflight class Sim impl api.IOpenAI {
pub createCompletion(prompt: str, params: api.CompletionParams?): str {
return Json.stringify({ mock: { prompt: prompt, params: params } });
}
}

pub class OpenAI impl api.IOpenAI {
apiKey: cloud.Secret?;
org: cloud.Secret?;
keyOverride: str?;
orgOverride: str?;

mock: bool;

inflight openai: api.IOpenAI;

new (apiKey: str?, org: str?, apiKeySecret: cloud.Secret?, orgSecret: cloud.Secret?) {
this.apiKey = apiKeySecret;
this.org = orgSecret;
this.keyOverride = apiKey;
this.orgOverride = org;
new(props: OpenAIProps?) {
this.apiKey = props?.apiKeySecret;
this.org = props?.orgSecret;
this.keyOverride = props?.apiKey;
this.orgOverride = props?.org;
this.mock = util.env("WING_TARGET") == "sim" && nodeof(this).app.isTestEnvironment;
}

inflight new() {
// I wanted to write this thing like this:
// let apiKey: str? = this.keyOverride ?? this.apiKey?.value();
// I was even willing to settle for this:
// let apiKey: str? = this.keyOverride ?? this.apiKey?.value();
// But neither of those work. So I'm doing this instead after opening issue https://github.com/winglang/wing/issues/5944:
let var apiKey = this.keyOverride;
if (apiKey == nil){
apiKey = this.apiKey?.value();
}
if (apiKey == nil || apiKey == ""){
throw "OpenAI API key is required";
// TODO: https://github.com/winglang/wing/issues/5944
let UNDEFINED = "<UNDEFINED>";
let apiKey = this.keyOverride ?? this.apiKey?.value() ?? UNDEFINED;
if apiKey == UNDEFINED {
throw "Either `apiKeySecret` or `apiKey` are required";
}

let var org: str? = this.orgOverride;
if (org == nil){
if org == nil {
org = this.org?.value();
}

this.openai = utils.createNewInflightClient(apiKey!, org);

if this.mock {
this.openai = new Sim();
} else {
this.openai = utils.createNewInflightClient(apiKey, org);
}
}

pub inflight createCompletion(prompt: str, params: api.CompletionParams?): str {
Expand Down
Loading

0 comments on commit 6aebb61

Please sign in to comment.