From d5a161be1589165b104f2d50a4450cfa604a8e5b Mon Sep 17 00:00:00 2001 From: Shai Ber Date: Thu, 14 Mar 2024 16:09:05 +0200 Subject: [PATCH 01/16] Add openai lib --- .github/workflows/canary.yaml | 21 ++ .github/workflows/openai-pull.yaml | 29 +++ .github/workflows/openai-release.yaml | 54 +++++ .gitignore | 1 + .mergify.yml | 6 + README.md | 9 + openai/.gitignore | 3 + openai/LICENSE | 21 ++ openai/README.md | 45 ++++ openai/api.w | 9 + openai/openai.js | 42 ++++ openai/openai.test.w | 21 ++ openai/openai.w | 45 ++++ openai/package-lock.json | 290 ++++++++++++++++++++++++++ openai/package.json | 17 ++ openai/utils.w | 5 + 16 files changed, 618 insertions(+) create mode 100644 .github/workflows/openai-pull.yaml create mode 100644 .github/workflows/openai-release.yaml create mode 100644 openai/.gitignore create mode 100644 openai/LICENSE create mode 100644 openai/README.md create mode 100644 openai/api.w create mode 100644 openai/openai.js create mode 100644 openai/openai.test.w create mode 100644 openai/openai.w create mode 100644 openai/package-lock.json create mode 100644 openai/package.json create mode 100644 openai/utils.w diff --git a/.github/workflows/canary.yaml b/.github/workflows/canary.yaml index 0b74431f..59242098 100644 --- a/.github/workflows/canary.yaml +++ b/.github/workflows/canary.yaml @@ -130,6 +130,27 @@ jobs: - name: Test run: wing test working-directory: ngrok + canary-openai: + name: Test openai + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + sparse-checkout: openai + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: 20.x + registry-url: https://registry.npmjs.org + - name: Install winglang + run: npm i -g winglang + - name: Install dependencies + run: npm install --include=dev + working-directory: openai + - name: Test + run: wing test + working-directory: openai canary-postgres: name: Test postgres runs-on: ubuntu-latest diff --git a/.github/workflows/openai-pull.yaml b/.github/workflows/openai-pull.yaml new file mode 100644 index 00000000..ddadbd14 --- /dev/null +++ b/.github/workflows/openai-pull.yaml @@ -0,0 +1,29 @@ +name: openai-pull +on: + pull_request: + paths: + - openai/** +jobs: + build-openai: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + sparse-checkout: openai + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: 20.x + registry-url: https://registry.npmjs.org + - name: Install winglang + run: npm i -g winglang + - name: Install dependencies + run: npm install --include=dev + working-directory: openai + - name: Test + run: wing test + working-directory: openai + - name: Pack + run: wing pack + working-directory: openai diff --git a/.github/workflows/openai-release.yaml b/.github/workflows/openai-release.yaml new file mode 100644 index 00000000..3f61b2fe --- /dev/null +++ b/.github/workflows/openai-release.yaml @@ -0,0 +1,54 @@ +name: openai-release +on: + push: + branches: + - main + paths: + - openai/** + - "!openai/package-lock.json" +jobs: + build-openai: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + sparse-checkout: openai + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: 20.x + registry-url: https://registry.npmjs.org + - name: Install winglang + run: npm i -g winglang + - name: Install dependencies + run: npm install --include=dev + working-directory: openai + - name: Test + run: wing test + working-directory: openai + - name: Pack + run: wing pack + working-directory: openai + - name: Get package version + run: echo WINGLIB_VERSION=$(node -p "require('./package.json').version") >> + "$GITHUB_ENV" + working-directory: openai + - name: Publish + run: npm publish --access=public --registry https://registry.npmjs.org --tag + latest *.tgz + working-directory: openai + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + - name: Tag commit + uses: tvdias/github-tagger@v0.0.1 + with: + repo-token: ${{ secrets.PROJEN_GITHUB_TOKEN }} + tag: openai-v${{ env.WINGLIB_VERSION }} + - name: GitHub release + uses: softprops/action-gh-release@v1 + with: + name: openai v${{ env.WINGLIB_VERSION }} + tag_name: openai-v${{ env.WINGLIB_VERSION }} + files: "*.tgz" + token: ${{ secrets.PROJEN_GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index 783e1112..6a5f24e9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ **/node_modules/** target/ **/.env +.history/ diff --git a/.mergify.yml b/.mergify.yml index 883ffa44..b10827f4 100644 --- a/.mergify.yml +++ b/.mergify.yml @@ -53,6 +53,9 @@ pull_request_rules: - -check-failure=build-ngrok - -check-pending=build-ngrok - -check-stale=build-ngrok + - -check-failure=build-openai + - -check-pending=build-openai + - -check-stale=build-openai - -check-failure=build-postgres - -check-pending=build-postgres - -check-stale=build-postgres @@ -104,6 +107,9 @@ pull_request_rules: - -check-failure=build-ngrok - -check-pending=build-ngrok - -check-stale=build-ngrok + - -check-failure=build-openai + - -check-pending=build-openai + - -check-stale=build-openai - -check-failure=build-postgres - -check-pending=build-postgres - -check-stale=build-postgres diff --git a/README.md b/README.md index c2d0bf00..34721530 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,12 @@ Clone this repository: git clone git@github.com:winglang/winglibs ``` +Change to the `winglibs` directory: + +```sh +cd winglibs +``` + Use the fabulous `mklib.sh` script to scaffold your library: ```sh @@ -74,6 +80,9 @@ bring my-awesome-lib ## Writing Wing Libraries See [docs](https://www.winglang.io/docs/libraries#creating-a-wing-library). + +Please note that it refers to writing libraries that are not published here, so it includes instructions for things that you get here automatically when using the `mklib.sh` script to scaffold your library. + ## License This repository is licensed under the [MIT License](./LICENSE), unless otherwise specified in a diff --git a/openai/.gitignore b/openai/.gitignore new file mode 100644 index 00000000..cd2c33bc --- /dev/null +++ b/openai/.gitignore @@ -0,0 +1,3 @@ +target/ +node_modules/ +.history/ diff --git a/openai/LICENSE b/openai/LICENSE new file mode 100644 index 00000000..a875f479 --- /dev/null +++ b/openai/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Wing + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/openai/README.md b/openai/README.md new file mode 100644 index 00000000..36bdc3d7 --- /dev/null +++ b/openai/README.md @@ -0,0 +1,45 @@ +# openai + +## Prerequisites + +* [winglang](https://winglang.io). + +## Installation + +`sh +npm i @winglibs/openai +` + +## Example + +`js +bring openai; + +let openAIService = new openai.OpenAI("your-api-key"); + +new cloud.Function(inflight () => { + let joke = openAIService.createCompletion("tell me a short joke"); + log(joke); +}); +` + +## Usage +The `openai.OpenAI` resource provides the following inflight methods: + +* `createCompletion` - Gets an answer to a prompt. + +The preflight constructor requires an api key in the form of either a secret or a string. + +## Roadmap + +* [x] Support the rest of the openai API +* [ ] Add more examples +* [ ] Add more tests + +## Maintainers + +* [Shai Ber](https://github.com/shaiber) + +## License + +Licensed under the [MIT License](/LICENSE). diff --git a/openai/api.w b/openai/api.w new file mode 100644 index 00000000..8a61690e --- /dev/null +++ b/openai/api.w @@ -0,0 +1,9 @@ +pub struct CompletionParams { + model: str; + max_tokens: num; +} + +// TODO: need to recreate the openai interface with higher fidelity +pub interface IOpenAI extends std.IResource { + inflight createCompletion(prompt: str, params: CompletionParams?): str; +} diff --git a/openai/openai.js b/openai/openai.js new file mode 100644 index 00000000..25eda35d --- /dev/null +++ b/openai/openai.js @@ -0,0 +1,42 @@ +const openai = require('openai'); + +exports.createNewInflightClient = (apiKey, org) => { + const config = { + apiKey: apiKey + }; + + if (org) { + config.organization = org; + } + + let client = new openai.OpenAI(config); + + // 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 }) => { + if (!prompt) { + throw new Error("Prompt is required"); + }; + + if (typeof prompt !== "string") { + throw new Error("Prompt must be a string"); + } + + if (!params.model) { + params.model = "gpt-3.5-turbo"; + }; + + if (!params.max_tokens) { + params.max_tokens = 2048; + } + + params.messages = [{role: 'user', content: prompt}]; + + const response = await client.chat.completions.create(params); + + return response.choices[0]?.message?.content; + } + }; +}; + diff --git a/openai/openai.test.w b/openai/openai.test.w new file mode 100644 index 00000000..bbf2628c --- /dev/null +++ b/openai/openai.test.w @@ -0,0 +1,21 @@ +bring expect; +bring "./openai.w" as openai; + +let client = new openai.OpenAI(); + +// 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"); +// } + +// 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); +// } diff --git a/openai/openai.w b/openai/openai.w new file mode 100644 index 00000000..58433b17 --- /dev/null +++ b/openai/openai.w @@ -0,0 +1,45 @@ +bring util; +bring "./api.w" as api; +bring "./utils.w" as utils; +bring cloud; + +pub class OpenAI impl api.IOpenAI { + apiKey: cloud.Secret?; + org: cloud.Secret?; + keyOverride: str?; + orgOverride: str?; + + 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; + } + + 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"; + } + let var org: str? = this.orgOverride; + if (org == nil){ + org = this.org?.value(); + } + + this.openai = utils.createNewInflightClient(apiKey!, org); + } + + pub inflight createCompletion(prompt: str, params: api.CompletionParams?): str { + return this.openai.createCompletion(prompt, params); + } +} diff --git a/openai/package-lock.json b/openai/package-lock.json new file mode 100644 index 00000000..0a52ec44 --- /dev/null +++ b/openai/package-lock.json @@ -0,0 +1,290 @@ +{ + "name": "@winglibs/openai", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@winglibs/openai", + "version": "0.0.1", + "license": "MIT", + "dependencies": { + "openai": "^4.28.4" + } + }, + "node_modules/@types/node": { + "version": "18.19.24", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.24.tgz", + "integrity": "sha512-eghAz3gnbQbvnHqB+mgB2ZR3aH6RhdEmHGS48BnV75KceQPHqabkxKI0BbUSsqhqy2Ddhc2xD/VAR9ySZd57Lw==", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/node-fetch": { + "version": "2.6.11", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.11.tgz", + "integrity": "sha512-24xFj9R5+rfQJLRyM56qh+wnVSYhyXC2tkoBndtY0U+vubqNsYXGjufB2nn8Q6gt0LrARwL6UBtMCSVCwl4B1g==", + "dependencies": { + "@types/node": "*", + "form-data": "^4.0.0" + } + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/agentkeepalive": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.5.0.tgz", + "integrity": "sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==", + "dependencies": { + "humanize-ms": "^1.2.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/base-64": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/base-64/-/base-64-0.1.0.tgz", + "integrity": "sha512-Y5gU45svrR5tI2Vt/X9GPd3L0HNIKzGu202EjxrXMpuc2V2CiKgemAbUUsqYmZJvPtCXoUKjNZwBJzsNScUbXA==" + }, + "node_modules/charenc": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", + "integrity": "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==", + "engines": { + "node": "*" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/crypt": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz", + "integrity": "sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==", + "engines": { + "node": "*" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/digest-fetch": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/digest-fetch/-/digest-fetch-1.3.0.tgz", + "integrity": "sha512-CGJuv6iKNM7QyZlM2T3sPAdZWd/p9zQiRNS9G+9COUCwzWFTs0Xp8NF5iePx7wtvhDykReiRRrSeNb4oMmB8lA==", + "dependencies": { + "base-64": "^0.1.0", + "md5": "^2.3.0" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/form-data-encoder": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz", + "integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==" + }, + "node_modules/formdata-node": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz", + "integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==", + "dependencies": { + "node-domexception": "1.0.0", + "web-streams-polyfill": "4.0.0-beta.3" + }, + "engines": { + "node": ">= 12.20" + } + }, + "node_modules/formdata-node/node_modules/web-streams-polyfill": { + "version": "4.0.0-beta.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz", + "integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==", + "engines": { + "node": ">= 14" + } + }, + "node_modules/humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", + "dependencies": { + "ms": "^2.0.0" + } + }, + "node_modules/is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" + }, + "node_modules/md5": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz", + "integrity": "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==", + "dependencies": { + "charenc": "0.0.2", + "crypt": "0.0.2", + "is-buffer": "~1.1.6" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/openai": { + "version": "4.28.4", + "resolved": "https://registry.npmjs.org/openai/-/openai-4.28.4.tgz", + "integrity": "sha512-RNIwx4MT/F0zyizGcwS+bXKLzJ8QE9IOyigDG/ttnwB220d58bYjYFp0qjvGwEFBO6+pvFVIDABZPGDl46RFsg==", + "dependencies": { + "@types/node": "^18.11.18", + "@types/node-fetch": "^2.6.4", + "abort-controller": "^3.0.0", + "agentkeepalive": "^4.2.1", + "digest-fetch": "^1.3.0", + "form-data-encoder": "1.7.2", + "formdata-node": "^4.3.2", + "node-fetch": "^2.6.7", + "web-streams-polyfill": "^3.2.1" + }, + "bin": { + "openai": "bin/cli" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" + }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + } + } +} diff --git a/openai/package.json b/openai/package.json new file mode 100644 index 00000000..1b4621ad --- /dev/null +++ b/openai/package.json @@ -0,0 +1,17 @@ +{ + "name": "@winglibs/openai", + "version": "0.0.1", + "repository": { + "type": "git", + "url": "https://github.com/winglang/winglibs.git", + "directory": "openai" + }, + "author": { + "email": "shaib@wing.cloud", + "name": "Shai Ber" + }, + "license": "MIT", + "dependencies": { + "openai": "^4.28.4" + } +} diff --git a/openai/utils.w b/openai/utils.w new file mode 100644 index 00000000..1f080534 --- /dev/null +++ b/openai/utils.w @@ -0,0 +1,5 @@ +bring "./api.w" as api; + +pub class Util { + extern "./openai.js" pub static inflight createNewInflightClient(apiKey: str, org: str?): api.IOpenAI; +} \ No newline at end of file From 8dea801a31f91381287871ac5dbc4759183d4c1b Mon Sep 17 00:00:00 2001 From: Shai Ber Date: Thu, 14 Mar 2024 16:29:50 +0200 Subject: [PATCH 02/16] change git clone in the readme to the public url --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 34721530..ff9571b3 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ It's so damn easy. Clone this repository: ```sh -git clone git@github.com:winglang/winglibs +git clone https://github.com/winglang/winglibs.git ``` Change to the `winglibs` directory: From 0764e68c86c17a68e94615f7bdebc3b114c79507 Mon Sep 17 00:00:00 2001 From: Shai Ber Date: Thu, 14 Mar 2024 17:52:14 +0200 Subject: [PATCH 03/16] add description to package.json --- openai/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/openai/package.json b/openai/package.json index 1b4621ad..f00c202a 100644 --- a/openai/package.json +++ b/openai/package.json @@ -1,5 +1,6 @@ { "name": "@winglibs/openai", + "description": "openai library for Wing", "version": "0.0.1", "repository": { "type": "git", From ccbf945038f9e813f69e0af89226d0b5d1ef9434 Mon Sep 17 00:00:00 2001 From: Shai Ber Date: Thu, 14 Mar 2024 18:16:21 +0200 Subject: [PATCH 04/16] update mklib.sh to include a description in package.json --- mklib.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/mklib.sh b/mklib.sh index f732bd1d..9ab90d62 100755 --- a/mklib.sh +++ b/mklib.sh @@ -8,6 +8,7 @@ mkdir $1 cat > $1/package.json < Date: Mon, 18 Mar 2024 17:53:36 +0200 Subject: [PATCH 05/16] fix PR comment about peerDependencies --- mklib.sh | 4 ++++ openai/package.json | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/mklib.sh b/mklib.sh index 9ab90d62..d7497ffd 100755 --- a/mklib.sh +++ b/mklib.sh @@ -15,6 +15,10 @@ cat > $1/package.json <", + "name": "" + }, "license": "MIT" } HERE diff --git a/openai/package.json b/openai/package.json index f00c202a..9ddb2708 100644 --- a/openai/package.json +++ b/openai/package.json @@ -12,7 +12,7 @@ "name": "Shai Ber" }, "license": "MIT", - "dependencies": { + "peerDependencies": { "openai": "^4.28.4" } } From 6aebb612f09c5294dc414942c4dcaebbfba050e0 Mon Sep 17 00:00:00 2001 From: Elad Ben-Israel Date: Tue, 19 Mar 2024 11:56:06 +0200 Subject: [PATCH 06/16] tweaks --- openai/README.md | 54 +++++++++++++++++++++++++++++++-------- openai/api.w | 2 +- openai/example.main.w | 12 +++++++++ openai/openai.extern.d.ts | 10 ++++++++ openai/openai.js | 10 +++----- openai/openai.test.w | 30 ++++++++++------------ openai/openai.w | 53 ++++++++++++++++++++++++-------------- openai/package-lock.json | 48 +++++++++++++++++++++++++++------- openai/package.json | 2 +- 9 files changed, 158 insertions(+), 63 deletions(-) create mode 100644 openai/example.main.w create mode 100644 openai/openai.extern.d.ts diff --git a/openai/README.md b/openai/README.md index 36bdc3d7..83533ef5 100644 --- a/openai/README.md +++ b/openai/README.md @@ -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 diff --git a/openai/api.w b/openai/api.w index 8a61690e..9ea0c07e 100644 --- a/openai/api.w +++ b/openai/api.w @@ -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; } diff --git a/openai/example.main.w b/openai/example.main.w new file mode 100644 index 00000000..e42c3ae7 --- /dev/null +++ b/openai/example.main.w @@ -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"; diff --git a/openai/openai.extern.d.ts b/openai/openai.extern.d.ts new file mode 100644 index 00000000..ddb1e323 --- /dev/null +++ b/openai/openai.extern.d.ts @@ -0,0 +1,10 @@ +export default interface extern { + createNewInflightClient: (apiKey: string, org?: (string) | undefined) => Promise, +} +export interface CompletionParams { + readonly max_tokens: number; + readonly model: string; +} +export interface IOpenAI$Inflight { + readonly createCompletion: (prompt: string, params?: (CompletionParams) | undefined) => Promise; +} \ No newline at end of file diff --git a/openai/openai.js b/openai/openai.js index 25eda35d..0f6c0298 100644 --- a/openai/openai.js +++ b/openai/openai.js @@ -1,9 +1,7 @@ const openai = require('openai'); exports.createNewInflightClient = (apiKey, org) => { - const config = { - apiKey: apiKey - }; + const config = { apiKey }; if (org) { config.organization = org; @@ -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"); }; @@ -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; } }; }; - diff --git a/openai/openai.test.w b/openai/openai.test.w index bbf2628c..21299d4a 100644 --- a/openai/openai.test.w +++ b/openai/openai.test.w @@ -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} + } + })); +} diff --git a/openai/openai.w b/openai/openai.w index 58433b17..be6a4737 100644 --- a/openai/openai.w +++ b/openai/openai.w @@ -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 = ""; + 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 { diff --git a/openai/package-lock.json b/openai/package-lock.json index 0a52ec44..ea549f32 100644 --- a/openai/package-lock.json +++ b/openai/package-lock.json @@ -8,7 +8,7 @@ "name": "@winglibs/openai", "version": "0.0.1", "license": "MIT", - "dependencies": { + "peerDependencies": { "openai": "^4.28.4" } }, @@ -16,6 +16,7 @@ "version": "18.19.24", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.24.tgz", "integrity": "sha512-eghAz3gnbQbvnHqB+mgB2ZR3aH6RhdEmHGS48BnV75KceQPHqabkxKI0BbUSsqhqy2Ddhc2xD/VAR9ySZd57Lw==", + "peer": true, "dependencies": { "undici-types": "~5.26.4" } @@ -24,6 +25,7 @@ "version": "2.6.11", "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.11.tgz", "integrity": "sha512-24xFj9R5+rfQJLRyM56qh+wnVSYhyXC2tkoBndtY0U+vubqNsYXGjufB2nn8Q6gt0LrARwL6UBtMCSVCwl4B1g==", + "peer": true, "dependencies": { "@types/node": "*", "form-data": "^4.0.0" @@ -33,6 +35,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "peer": true, "dependencies": { "event-target-shim": "^5.0.0" }, @@ -44,6 +47,7 @@ "version": "4.5.0", "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.5.0.tgz", "integrity": "sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==", + "peer": true, "dependencies": { "humanize-ms": "^1.2.1" }, @@ -54,17 +58,20 @@ "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "peer": true }, "node_modules/base-64": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/base-64/-/base-64-0.1.0.tgz", - "integrity": "sha512-Y5gU45svrR5tI2Vt/X9GPd3L0HNIKzGu202EjxrXMpuc2V2CiKgemAbUUsqYmZJvPtCXoUKjNZwBJzsNScUbXA==" + "integrity": "sha512-Y5gU45svrR5tI2Vt/X9GPd3L0HNIKzGu202EjxrXMpuc2V2CiKgemAbUUsqYmZJvPtCXoUKjNZwBJzsNScUbXA==", + "peer": true }, "node_modules/charenc": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", "integrity": "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==", + "peer": true, "engines": { "node": "*" } @@ -73,6 +80,7 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "peer": true, "dependencies": { "delayed-stream": "~1.0.0" }, @@ -84,6 +92,7 @@ "version": "0.0.2", "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz", "integrity": "sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==", + "peer": true, "engines": { "node": "*" } @@ -92,6 +101,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "peer": true, "engines": { "node": ">=0.4.0" } @@ -100,6 +110,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/digest-fetch/-/digest-fetch-1.3.0.tgz", "integrity": "sha512-CGJuv6iKNM7QyZlM2T3sPAdZWd/p9zQiRNS9G+9COUCwzWFTs0Xp8NF5iePx7wtvhDykReiRRrSeNb4oMmB8lA==", + "peer": true, "dependencies": { "base-64": "^0.1.0", "md5": "^2.3.0" @@ -109,6 +120,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "peer": true, "engines": { "node": ">=6" } @@ -117,6 +129,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "peer": true, "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", @@ -129,12 +142,14 @@ "node_modules/form-data-encoder": { "version": "1.7.2", "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz", - "integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==" + "integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==", + "peer": true }, "node_modules/formdata-node": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz", "integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==", + "peer": true, "dependencies": { "node-domexception": "1.0.0", "web-streams-polyfill": "4.0.0-beta.3" @@ -147,6 +162,7 @@ "version": "4.0.0-beta.3", "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz", "integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==", + "peer": true, "engines": { "node": ">= 14" } @@ -155,6 +171,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", + "peer": true, "dependencies": { "ms": "^2.0.0" } @@ -162,12 +179,14 @@ "node_modules/is-buffer": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "peer": true }, "node_modules/md5": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz", "integrity": "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==", + "peer": true, "dependencies": { "charenc": "0.0.2", "crypt": "0.0.2", @@ -178,6 +197,7 @@ "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "peer": true, "engines": { "node": ">= 0.6" } @@ -186,6 +206,7 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "peer": true, "dependencies": { "mime-db": "1.52.0" }, @@ -196,7 +217,8 @@ "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "peer": true }, "node_modules/node-domexception": { "version": "1.0.0", @@ -212,6 +234,7 @@ "url": "https://paypal.me/jimmywarting" } ], + "peer": true, "engines": { "node": ">=10.5.0" } @@ -220,6 +243,7 @@ "version": "2.7.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "peer": true, "dependencies": { "whatwg-url": "^5.0.0" }, @@ -239,6 +263,7 @@ "version": "4.28.4", "resolved": "https://registry.npmjs.org/openai/-/openai-4.28.4.tgz", "integrity": "sha512-RNIwx4MT/F0zyizGcwS+bXKLzJ8QE9IOyigDG/ttnwB220d58bYjYFp0qjvGwEFBO6+pvFVIDABZPGDl46RFsg==", + "peer": true, "dependencies": { "@types/node": "^18.11.18", "@types/node-fetch": "^2.6.4", @@ -257,17 +282,20 @@ "node_modules/tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "peer": true }, "node_modules/undici-types": { "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "peer": true }, "node_modules/web-streams-polyfill": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "peer": true, "engines": { "node": ">= 8" } @@ -275,12 +303,14 @@ "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "peer": true }, "node_modules/whatwg-url": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "peer": true, "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" diff --git a/openai/package.json b/openai/package.json index 9ddb2708..3319e2da 100644 --- a/openai/package.json +++ b/openai/package.json @@ -1,6 +1,6 @@ { "name": "@winglibs/openai", - "description": "openai library for Wing", + "description": "OpenAI library for Wing", "version": "0.0.1", "repository": { "type": "git", From 3ca902330ce479017ea07c14b5a48009de75aa7e Mon Sep 17 00:00:00 2001 From: Elad Ben-Israel Date: Tue, 19 Mar 2024 11:57:35 +0200 Subject: [PATCH 07/16] remove author from template --- mklib.sh | 4 ---- 1 file changed, 4 deletions(-) diff --git a/mklib.sh b/mklib.sh index d7497ffd..9ab90d62 100755 --- a/mklib.sh +++ b/mklib.sh @@ -15,10 +15,6 @@ cat > $1/package.json <", - "name": "" - }, "license": "MIT" } HERE From d4fb8ed9f5ad9ee1fed1c4a9eb78192a37d09fca Mon Sep 17 00:00:00 2001 From: Elad Ben-Israel Date: Tue, 19 Mar 2024 11:58:10 +0200 Subject: [PATCH 08/16] Update openai.test.w --- openai/openai.test.w | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openai/openai.test.w b/openai/openai.test.w index 21299d4a..df34cf34 100644 --- a/openai/openai.test.w +++ b/openai/openai.test.w @@ -7,13 +7,13 @@ let key = new cloud.Secret(name: "my-openai-key"); let oai = new openai.OpenAI(apiKeySecret: key); test "basic completion" { - let answer = oai.createCompletion("tell me a short joke", model: "gpt-3.5-turbo", max_tokens: 2048); + let answer = oai.createCompletion("tell me a short joke", max_tokens: 1024); // 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} + params:{"model":"gpt-3.5-turbo","max_tokens":1024} } })); } From 0077892d4a4fbd9087c037c0bdef7edda6ff56df Mon Sep 17 00:00:00 2001 From: Shai Ber Date: Tue, 19 Mar 2024 14:04:48 +0200 Subject: [PATCH 09/16] fix test --- openai/openai.test.w | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openai/openai.test.w b/openai/openai.test.w index df34cf34..b32f9272 100644 --- a/openai/openai.test.w +++ b/openai/openai.test.w @@ -7,7 +7,7 @@ let key = new cloud.Secret(name: "my-openai-key"); let oai = new openai.OpenAI(apiKeySecret: key); test "basic completion" { - let answer = oai.createCompletion("tell me a short joke", max_tokens: 1024); + let answer = oai.createCompletion("tell me a short joke", max_tokens: 1024, model :"gpt-3.5-turbo"); // in tests, the response is just an echo of the request expect.equal(answer, Json.stringify({ From e3fef6a70177bd0c6285497f2eda66cc9062d730 Mon Sep 17 00:00:00 2001 From: Shai Ber Date: Tue, 19 Mar 2024 14:10:24 +0200 Subject: [PATCH 10/16] another fix to test --- openai/example.main.w | 3 +-- openai/openai.test.w | 6 +++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/openai/example.main.w b/openai/example.main.w index e42c3ae7..ea969c12 100644 --- a/openai/example.main.w +++ b/openai/example.main.w @@ -3,8 +3,7 @@ bring "./openai.w" as openai; bring cloud; -let key = new cloud.Secret(name: "my-openai-key"); -let oai = new openai.OpenAI(apiKeySecret: key); +let oai = new openai.OpenAI(apiKey: "my-openai-key"); new cloud.Function(inflight () => { let answer = oai.createCompletion("tell me a short joke", model: "gpt-3.5-turbo", max_tokens: 2048); diff --git a/openai/openai.test.w b/openai/openai.test.w index b32f9272..5ab63fe7 100644 --- a/openai/openai.test.w +++ b/openai/openai.test.w @@ -3,11 +3,11 @@ bring "./openai.w" as openai; bring cloud; -let key = new cloud.Secret(name: "my-openai-key"); -let oai = new openai.OpenAI(apiKeySecret: key); +let key = "my-openai-key"; +let oai = new openai.OpenAI(apiKey: key); test "basic completion" { - let answer = oai.createCompletion("tell me a short joke", max_tokens: 1024, model :"gpt-3.5-turbo"); + let answer = oai.createCompletion("tell me a short joke", model :"gpt-3.5-turbo", max_tokens: 1024); // in tests, the response is just an echo of the request expect.equal(answer, Json.stringify({ From de79e2dc3e14231bfa18e7154cba3857eb8d6b54 Mon Sep 17 00:00:00 2001 From: Elad Ben-Israel Date: Tue, 19 Mar 2024 15:39:01 +0200 Subject: [PATCH 11/16] more cleanups --- openai/api.w | 9 -------- openai/example.main.w | 2 +- openai/openai.extern.d.ts | 10 +++------ openai/openai.js | 25 ++------------------- openai/openai.test.w | 7 +++--- openai/openai.w | 47 +++++++++++++++++++++++++++++---------- openai/utils.w | 5 ----- 7 files changed, 45 insertions(+), 60 deletions(-) delete mode 100644 openai/api.w delete mode 100644 openai/utils.w diff --git a/openai/api.w b/openai/api.w deleted file mode 100644 index 9ea0c07e..00000000 --- a/openai/api.w +++ /dev/null @@ -1,9 +0,0 @@ -pub struct CompletionParams { - model: str; - max_tokens: num; -} - -// TODO: need to recreate the openai interface with higher fidelity -pub interface IOpenAI { - inflight createCompletion(prompt: str, params: CompletionParams?): str; -} diff --git a/openai/example.main.w b/openai/example.main.w index e42c3ae7..5edfb0b2 100644 --- a/openai/example.main.w +++ b/openai/example.main.w @@ -7,6 +7,6 @@ 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); + let answer = oai.createCompletion("tell me a short joke", model: "gpt-3.5-turbo", maxTokens: 2048); log(answer); }) as "tell me a joke"; diff --git a/openai/openai.extern.d.ts b/openai/openai.extern.d.ts index ddb1e323..d3495fa1 100644 --- a/openai/openai.extern.d.ts +++ b/openai/openai.extern.d.ts @@ -1,10 +1,6 @@ export default interface extern { - createNewInflightClient: (apiKey: string, org?: (string) | undefined) => Promise, + createNewInflightClient: (apiKey: string, org?: (string) | undefined) => Promise, } -export interface CompletionParams { - readonly max_tokens: number; - readonly model: string; -} -export interface IOpenAI$Inflight { - readonly createCompletion: (prompt: string, params?: (CompletionParams) | undefined) => Promise; +export interface IClient$Inflight { + readonly createCompletion: (params: Readonly) => Promise>; } \ No newline at end of file diff --git a/openai/openai.js b/openai/openai.js index 0f6c0298..052dfeb2 100644 --- a/openai/openai.js +++ b/openai/openai.js @@ -9,30 +9,9 @@ exports.createNewInflightClient = (apiKey, org) => { let client = new openai.OpenAI(config); - // 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 = {}) => { - if (!prompt) { - throw new Error("Prompt is required"); - }; - - if (typeof prompt !== "string") { - throw new Error("Prompt must be a string"); - } - - if (!params.model) { - params.model = "gpt-3.5-turbo"; - }; - - if (!params.max_tokens) { - params.max_tokens = 2048; - } - - params.messages = [ { role: 'user', content: prompt } ]; - - const response = await client.chat.completions.create(params); - return response.choices[0]?.message?.content; + createCompletion: async params => { + return await client.chat.completions.create(params); } }; }; diff --git a/openai/openai.test.w b/openai/openai.test.w index df34cf34..9e236500 100644 --- a/openai/openai.test.w +++ b/openai/openai.test.w @@ -7,13 +7,14 @@ let key = new cloud.Secret(name: "my-openai-key"); let oai = new openai.OpenAI(apiKeySecret: key); test "basic completion" { - let answer = oai.createCompletion("tell me a short joke", max_tokens: 1024); + let answer = oai.createCompletion("tell me a short joke", maxTokens: 1024); // 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":1024} + "max_tokens":1024, + "model":"gpt-3.5-turbo", + "messages":[{"role":"user","content":"tell me a short joke"}] } })); } diff --git a/openai/openai.w b/openai/openai.w index be6a4737..88f0582c 100644 --- a/openai/openai.w +++ b/openai/openai.w @@ -1,7 +1,10 @@ -bring util; -bring "./api.w" as api; -bring "./utils.w" as utils; bring cloud; +bring util; + +pub struct CompletionParams { + model: str?; + maxTokens: num?; +} pub struct OpenAIProps { apiKey: str?; @@ -10,13 +13,25 @@ pub struct OpenAIProps { 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 } }); +interface IClient { + inflight createCompletion(params: Json): Json; +} + +inflight class Sim impl IClient { + pub createCompletion(req: Json): Json { + return { + choices: [ + { + message: { + content: Json.stringify({ mock: req }) + } + } + ] + }; } } -pub class OpenAI impl api.IOpenAI { +pub class OpenAI { apiKey: cloud.Secret?; org: cloud.Secret?; keyOverride: str?; @@ -24,7 +39,7 @@ pub class OpenAI impl api.IOpenAI { mock: bool; - inflight openai: api.IOpenAI; + inflight openai: IClient; new(props: OpenAIProps?) { this.apiKey = props?.apiKeySecret; @@ -50,11 +65,19 @@ pub class OpenAI impl api.IOpenAI { if this.mock { this.openai = new Sim(); } else { - this.openai = utils.createNewInflightClient(apiKey, org); + this.openai = OpenAI.createNewInflightClient(apiKey, org); } } - pub inflight createCompletion(prompt: str, params: api.CompletionParams?): str { - return this.openai.createCompletion(prompt, params); + pub inflight createCompletion(prompt: str, params: CompletionParams?): str { + let resp = this.openai.createCompletion({ + max_tokens: params?.maxTokens ?? 2048, + model: params?.model ?? "gpt-3.5-turbo", + messages: [ { role: "user", content: prompt } ] + }); + + return resp.get("choices").getAt(0).get("message").get("content").asStr(); } -} + + extern "./openai.js" pub static inflight createNewInflightClient(apiKey: str, org: str?): IClient; +} \ No newline at end of file diff --git a/openai/utils.w b/openai/utils.w deleted file mode 100644 index 1f080534..00000000 --- a/openai/utils.w +++ /dev/null @@ -1,5 +0,0 @@ -bring "./api.w" as api; - -pub class Util { - extern "./openai.js" pub static inflight createNewInflightClient(apiKey: str, org: str?): api.IOpenAI; -} \ No newline at end of file From ddbb605e9b26efd00fd14ac69dd32ee0968f8c63 Mon Sep 17 00:00:00 2001 From: Elad Ben-Israel Date: Tue, 19 Mar 2024 15:45:00 +0200 Subject: [PATCH 12/16] Update package.json --- openai/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openai/package.json b/openai/package.json index 3319e2da..6ef7ca04 100644 --- a/openai/package.json +++ b/openai/package.json @@ -1,7 +1,7 @@ { "name": "@winglibs/openai", "description": "OpenAI library for Wing", - "version": "0.0.1", + "version": "0.0.2", "repository": { "type": "git", "url": "https://github.com/winglang/winglibs.git", From dcc07ccf49286b210982cfc822502c033b8ab4d2 Mon Sep 17 00:00:00 2001 From: Elad Ben-Israel Date: Tue, 19 Mar 2024 16:58:11 +0200 Subject: [PATCH 13/16] Update openai.test.w --- openai/openai.test.w | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openai/openai.test.w b/openai/openai.test.w index 9e236500..c22cee72 100644 --- a/openai/openai.test.w +++ b/openai/openai.test.w @@ -3,8 +3,7 @@ bring "./openai.w" as openai; bring cloud; -let key = new cloud.Secret(name: "my-openai-key"); -let oai = new openai.OpenAI(apiKeySecret: key); +let oai = new openai.OpenAI(apiKey: "dummy-key"); test "basic completion" { let answer = oai.createCompletion("tell me a short joke", maxTokens: 1024); From 99554606bee414bff7acb443e48261cf9f4c0ce7 Mon Sep 17 00:00:00 2001 From: Elad Ben-Israel Date: Tue, 19 Mar 2024 17:00:50 +0200 Subject: [PATCH 14/16] Rename openai/example.main.w to openai/examples/example.main.w --- openai/{ => examples}/example.main.w | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename openai/{ => examples}/example.main.w (100%) diff --git a/openai/example.main.w b/openai/examples/example.main.w similarity index 100% rename from openai/example.main.w rename to openai/examples/example.main.w From 8c5e057f0654271d30e0d9b373baf9bb553b81ee Mon Sep 17 00:00:00 2001 From: Shai Ber Date: Tue, 19 Mar 2024 17:13:58 +0200 Subject: [PATCH 15/16] fix bring statement in examples --- openai/examples/example.main.w | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openai/examples/example.main.w b/openai/examples/example.main.w index d5432f69..640540ce 100644 --- a/openai/examples/example.main.w +++ b/openai/examples/example.main.w @@ -1,5 +1,5 @@ bring expect; -bring "./openai.w" as openai; +bring "../openai.w" as openai; bring cloud; From cad72d74cc31ced18ee4377c86db5f19062179c7 Mon Sep 17 00:00:00 2001 From: Shai Ber Date: Tue, 19 Mar 2024 17:24:16 +0200 Subject: [PATCH 16/16] remove unused files --- openai/api.w | 9 --------- openai/utils.w | 5 ----- 2 files changed, 14 deletions(-) delete mode 100644 openai/api.w delete mode 100644 openai/utils.w diff --git a/openai/api.w b/openai/api.w deleted file mode 100644 index 9ea0c07e..00000000 --- a/openai/api.w +++ /dev/null @@ -1,9 +0,0 @@ -pub struct CompletionParams { - model: str; - max_tokens: num; -} - -// TODO: need to recreate the openai interface with higher fidelity -pub interface IOpenAI { - inflight createCompletion(prompt: str, params: CompletionParams?): str; -} diff --git a/openai/utils.w b/openai/utils.w deleted file mode 100644 index 1f080534..00000000 --- a/openai/utils.w +++ /dev/null @@ -1,5 +0,0 @@ -bring "./api.w" as api; - -pub class Util { - extern "./openai.js" pub static inflight createNewInflightClient(apiKey: str, org: str?): api.IOpenAI; -} \ No newline at end of file